diff --git a/abi/Errors.json b/abi/Errors.json index 56dcc0c3..f5bfbdac 100644 --- a/abi/Errors.json +++ b/abi/Errors.json @@ -214,6 +214,11 @@ "name": "UpgradeFailed", "type": "error" }, + { + "inputs": [], + "name": "ValueNotChanged", + "type": "error" + }, { "inputs": [], "name": "ZeroAddress", diff --git a/abi/IDepositDataManager.json b/abi/IDepositDataManager.json new file mode 100644 index 00000000..9be53c95 --- /dev/null +++ b/abi/IDepositDataManager.json @@ -0,0 +1,297 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "depositDataManager", + "type": "address" + } + ], + "name": "DepositDataManagerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "depositDataManager", + "type": "address" + } + ], + "name": "DepositDataMigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "name": "DepositDataRootUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "depositDataIndexes", + "outputs": [ + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "depositDataRoots", + "outputs": [ + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "getDepositDataManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "address", + "name": "depositDataManager", + "type": "address" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "validatorsRegistryRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "validators", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "internalType": "struct IKeeperValidators.ApprovalParams", + "name": "keeperParams", + "type": "tuple" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "name": "registerValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "validatorsRegistryRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "validators", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "internalType": "struct IKeeperValidators.ApprovalParams", + "name": "keeperParams", + "type": "tuple" + }, + { + "internalType": "uint256[]", + "name": "indexes", + "type": "uint256[]" + }, + { + "internalType": "bool[]", + "name": "proofFlags", + "type": "bool[]" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "name": "registerValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "depositDataManager", + "type": "address" + } + ], + "name": "setDepositDataManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "name": "setDepositDataRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/IEthBlocklistErc20Vault.json b/abi/IEthBlocklistErc20Vault.json index 7e33b6d8..a20105c3 100644 --- a/abi/IEthBlocklistErc20Vault.json +++ b/abi/IEthBlocklistErc20Vault.json @@ -1247,66 +1247,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1366,19 +1306,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "symbol", @@ -1601,32 +1528,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthBlocklistVault.json b/abi/IEthBlocklistVault.json index d15eaa96..681538a9 100644 --- a/abi/IEthBlocklistVault.json +++ b/abi/IEthBlocklistVault.json @@ -1029,66 +1029,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1148,19 +1088,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -1304,32 +1231,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthErc20Vault.json b/abi/IEthErc20Vault.json index d582bf55..7fe7e899 100644 --- a/abi/IEthErc20Vault.json +++ b/abi/IEthErc20Vault.json @@ -1171,66 +1171,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1277,19 +1217,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "symbol", @@ -1494,32 +1421,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthFoxVault.json b/abi/IEthFoxVault.json index 91220f71..8dfae5e2 100644 --- a/abi/IEthFoxVault.json +++ b/abi/IEthFoxVault.json @@ -837,66 +837,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -956,19 +896,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -1112,32 +1039,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthGenesisVault.json b/abi/IEthGenesisVault.json index fd94b983..0e979212 100644 --- a/abi/IEthGenesisVault.json +++ b/abi/IEthGenesisVault.json @@ -1040,66 +1040,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1146,19 +1086,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -1284,32 +1211,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthPrivErc20Vault.json b/abi/IEthPrivErc20Vault.json index 5af925c6..d3fd4667 100644 --- a/abi/IEthPrivErc20Vault.json +++ b/abi/IEthPrivErc20Vault.json @@ -1215,66 +1215,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1321,19 +1261,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1569,32 +1496,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthPrivVault.json b/abi/IEthPrivVault.json index d0dd07d1..9c184958 100644 --- a/abi/IEthPrivVault.json +++ b/abi/IEthPrivVault.json @@ -997,66 +997,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1103,19 +1043,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1272,32 +1199,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IEthVault.json b/abi/IEthVault.json index 291b3c09..aac3151e 100644 --- a/abi/IEthVault.json +++ b/abi/IEthVault.json @@ -953,66 +953,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -1059,19 +999,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -1197,32 +1124,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "vaultId", diff --git a/abi/IVaultEthStaking.json b/abi/IVaultEthStaking.json index f45b61b8..bb31b994 100644 --- a/abi/IVaultEthStaking.json +++ b/abi/IVaultEthStaking.json @@ -634,66 +634,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -740,19 +680,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -878,32 +805,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "withdrawableAssets", diff --git a/abi/IVaultValidators.json b/abi/IVaultValidators.json index 9baee071..c3d002ed 100644 --- a/abi/IVaultValidators.json +++ b/abi/IVaultValidators.json @@ -313,66 +313,6 @@ "internalType": "struct IKeeperValidators.ApprovalParams", "name": "keeperParams", "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "uint256[]", - "name": "indexes", - "type": "uint256[]" - }, - { - "internalType": "bool[]", - "name": "proofFlags", - "type": "bool[]" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" } ], "name": "registerValidators", @@ -419,19 +359,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_validatorsRoot", - "type": "bytes32" - } - ], - "name": "setValidatorsRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "totalAssets", @@ -506,32 +433,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "validatorIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "withdrawableAssets", diff --git a/contracts/interfaces/IDepositDataManager.sol b/contracts/interfaces/IDepositDataManager.sol new file mode 100644 index 00000000..2872de4c --- /dev/null +++ b/contracts/interfaces/IDepositDataManager.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity =0.8.22; + +import {IKeeperValidators} from './IKeeperValidators.sol'; + +/** + * @title IDepositDataManager + * @author StakeWise + * @notice Defines the interface for DepositDataManager + */ +interface IDepositDataManager { + /** + * @notice Event emitted on deposit data manager update + * @param vault The address of the vault + * @param depositDataManager The address of the new deposit data manager + */ + event DepositDataManagerUpdated(address indexed vault, address depositDataManager); + + /** + * @notice Event emitted on deposit data root update + * @param vault The address of the vault + * @param depositDataRoot The new deposit data Merkle tree root + */ + event DepositDataRootUpdated(address indexed vault, bytes32 depositDataRoot); + + /** + * @notice Event emitted on deposit data migration + * @param vault The address of the vault + * @param depositDataRoot The deposit data root + * @param validatorIndex The index of the next validator to be registered + * @param depositDataManager The address of the deposit data manager + */ + event DepositDataMigrated( + address indexed vault, + bytes32 depositDataRoot, + uint256 validatorIndex, + address depositDataManager + ); + + /** + * @notice The vault deposit data index + * @param vault The address of the vault + * @return validatorIndex The index of the next validator to be registered + */ + function depositDataIndexes(address vault) external view returns (uint256 validatorIndex); + + /** + * @notice The vault deposit data root + * @param vault The address of the vault + * @return depositDataRoot The deposit data root + */ + function depositDataRoots(address vault) external view returns (bytes32 depositDataRoot); + + /** + * @notice The vault deposit data manager. Defaults to the vault admin if not set. + * @param vault The address of the vault + * @return depositDataManager The address of the deposit data manager + */ + function getDepositDataManager(address vault) external view returns (address); + + /** + * @notice Function for setting the deposit data manager for the vault. Can only be called by the vault admin. + * @param vault The address of the vault + * @param depositDataManager The address of the new deposit data manager + */ + function setDepositDataManager(address vault, address depositDataManager) external; + + /** + * @notice Function for setting the deposit data root for the vault. Can only be called by the deposit data manager. + * @param vault The address of the vault + * @param depositDataRoot The new deposit data Merkle tree root + */ + function setDepositDataRoot(address vault, bytes32 depositDataRoot) external; + + /** + * @notice Function for registering single validator + * @param vault The address of the vault + * @param keeperParams The parameters for getting approval from Keeper oracles + * @param proof The proof used to verify that the validator is part of the deposit data merkle tree + */ + function registerValidator( + address vault, + IKeeperValidators.ApprovalParams calldata keeperParams, + bytes32[] calldata proof + ) external; + + /** + * @notice Function for registering multiple validators + * @param vault The address of the vault + * @param keeperParams The parameters for getting approval from Keeper oracles + * @param indexes The indexes of the leaves for the merkle tree multi proof verification + * @param proofFlags The multi proof flags for the merkle tree verification + * @param proof The proof used for the merkle tree verification + */ + function registerValidators( + address vault, + IKeeperValidators.ApprovalParams calldata keeperParams, + uint256[] calldata indexes, + bool[] calldata proofFlags, + bytes32[] calldata proof + ) external; + + /** + * @notice Function for migrating the deposit data of the Vault. Can only be called once by a vault during an upgrade. + * @param depositDataRoot The current deposit data root + * @param validatorIndex The current index of the next validator to be registered + * @param depositDataManager The address of the deposit data manager + */ + function migrate( + bytes32 depositDataRoot, + uint256 validatorIndex, + address depositDataManager + ) external; +} diff --git a/contracts/interfaces/IVaultEnterExit.sol b/contracts/interfaces/IVaultEnterExit.sol index 2c400024..1bd280cf 100644 --- a/contracts/interfaces/IVaultEnterExit.sol +++ b/contracts/interfaces/IVaultEnterExit.sol @@ -27,7 +27,7 @@ interface IVaultEnterExit is IVaultState { ); /** - * @notice Event emitted on redeem + * @notice Event emitted on redeem (deprecated) * @param owner The address that owns the shares * @param receiver The address that received withdrawn assets * @param assets The total number of withdrawn assets @@ -36,7 +36,7 @@ interface IVaultEnterExit is IVaultState { event Redeemed(address indexed owner, address indexed receiver, uint256 assets, uint256 shares); /** - * @notice Event emitted on shares added to the V1 exit queue + * @notice Event emitted on shares added to the V1 exit queue (deprecated) * @param owner The address that owns the shares * @param receiver The address that will receive withdrawn assets * @param positionTicket The exit queue ticket that was assigned to the position @@ -90,7 +90,7 @@ interface IVaultEnterExit is IVaultState { ) external returns (uint256 positionTicket); /** - * @notice Get the exit queue index to claim exited assets from + * @notice Get the exit queue index to claim exited assets from (deprecated) * @param positionTicket The exit queue position ticket to get the index for * @return The exit queue index that should be used to claim exited assets. * Returns -1 in case such index does not exist. diff --git a/contracts/interfaces/IVaultState.sol b/contracts/interfaces/IVaultState.sol index e82b1681..9a2fdb8e 100644 --- a/contracts/interfaces/IVaultState.sol +++ b/contracts/interfaces/IVaultState.sol @@ -12,7 +12,7 @@ import {IVaultFee} from './IVaultFee.sol'; */ interface IVaultState is IVaultFee { /** - * @notice Event emitted on checkpoint creation (V1 exit queue) + * @notice Event emitted on checkpoint creation (deprecated) * @param shares The number of burned shares * @param assets The amount of exited assets */ @@ -51,7 +51,7 @@ interface IVaultState is IVaultFee { function withdrawableAssets() external view returns (uint256); /** - * @notice Queued Shares + * @notice Queued Shares (deprecated) * @return The total number of shares queued for exit */ function queuedShares() external view returns (uint128); diff --git a/contracts/interfaces/IVaultValidators.sol b/contracts/interfaces/IVaultValidators.sol index 53cbcb21..fc04b637 100644 --- a/contracts/interfaces/IVaultValidators.sol +++ b/contracts/interfaces/IVaultValidators.sol @@ -26,7 +26,7 @@ interface IVaultValidators is IVaultAdmin, IVaultState { event KeysManagerUpdated(address indexed caller, address indexed keysManager); /** - * @notice Event emitted on validators merkle tree root update + * @notice Event emitted on validators merkle tree root update (deprecated) * @param caller The address of the function caller * @param validatorsRoot The new validators merkle tree root */ @@ -34,55 +34,19 @@ interface IVaultValidators is IVaultAdmin, IVaultState { /** * @notice The Vault keys manager address - * @return The address that can update validators merkle tree root + * @return The address that can register validators */ function keysManager() external view returns (address); /** - * @notice The Vault validators root - * @return The merkle tree root to use for verifying validators deposit data - */ - function validatorsRoot() external view returns (bytes32); - - /** - * @notice The Vault validator index - * @return The index of the next validator to be registered in the current deposit data file - */ - function validatorIndex() external view returns (uint256); - - /** - * @notice Function for registering single validator + * @notice Function for registering single or multiple validators * @param keeperParams The parameters for getting approval from Keeper oracles - * @param proof The proof used to verify that the validator is part of the validators merkle tree */ - function registerValidator( - IKeeperValidators.ApprovalParams calldata keeperParams, - bytes32[] calldata proof - ) external; + function registerValidators(IKeeperValidators.ApprovalParams calldata keeperParams) external; /** - * @notice Function for registering multiple validators - * @param keeperParams The parameters for getting approval from Keeper oracles - * @param indexes The indexes of the leaves for the merkle tree multi proof verification - * @param proofFlags The multi proof flags for the merkle tree verification - * @param proof The proof used for the merkle tree verification - */ - function registerValidators( - IKeeperValidators.ApprovalParams calldata keeperParams, - uint256[] calldata indexes, - bool[] calldata proofFlags, - bytes32[] calldata proof - ) external; - - /** - * @notice Function for updating the keys manager. Can only be called by the admin. + * @notice Function for updating the keys manager. Can only be called by the admin. Default is the deposit data manager contract. * @param _keysManager The new keys manager address */ function setKeysManager(address _keysManager) external; - - /** - * @notice Function for updating the validators merkle tree root. Can only be called by the keys manager. - * @param _validatorsRoot The new validators merkle tree root - */ - function setValidatorsRoot(bytes32 _validatorsRoot) external; } diff --git a/contracts/libraries/Errors.sol b/contracts/libraries/Errors.sol index 871c3544..902b922b 100644 --- a/contracts/libraries/Errors.sol +++ b/contracts/libraries/Errors.sol @@ -52,4 +52,5 @@ library Errors { error InvalidCheckpointValue(); error MaxOraclesExceeded(); error ExitRequestNotProcessed(); + error ValueNotChanged(); } diff --git a/contracts/misc/DepositDataManager.sol b/contracts/misc/DepositDataManager.sol new file mode 100644 index 00000000..05daae0e --- /dev/null +++ b/contracts/misc/DepositDataManager.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity =0.8.22; + +import {MerkleProof} from '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol'; +import {IDepositDataManager} from '../interfaces/IDepositDataManager.sol'; +import {IKeeperValidators} from '../interfaces/IKeeperValidators.sol'; +import {IVaultAdmin} from '../interfaces/IVaultAdmin.sol'; +import {IVaultValidators} from '../interfaces/IVaultValidators.sol'; +import {IVaultsRegistry} from '../interfaces/IVaultsRegistry.sol'; +import {Errors} from '../libraries/Errors.sol'; + +/** + * @title DepositDataManager + * @author StakeWise + * @notice Defines the functionality for the Vault's deposit data management + */ +contract DepositDataManager is IDepositDataManager { + IVaultsRegistry private immutable _vaultsRegistry; + + /// @inheritdoc IDepositDataManager + mapping(address => uint256) public override depositDataIndexes; + + /// @inheritdoc IDepositDataManager + mapping(address => bytes32) public override depositDataRoots; + + mapping(address => address) private _depositDataManagers; + mapping(address => bool) private _migrated; + + /** + * @dev Constructor + * @param vaultsRegistry The address of the vaults registry contract + */ + constructor(address vaultsRegistry) { + _vaultsRegistry = IVaultsRegistry(vaultsRegistry); + } + + /// @inheritdoc IDepositDataManager + function getDepositDataManager(address vault) public view override returns (address) { + address depositDataManager = _depositDataManagers[vault]; + return depositDataManager == address(0) ? IVaultAdmin(vault).admin() : depositDataManager; + } + + /// @inheritdoc IDepositDataManager + function setDepositDataManager(address vault, address depositDataManager) external override { + if (!_vaultsRegistry.vaults(vault)) revert Errors.InvalidVault(); + // only vault admin can set deposit data manager + if (msg.sender != IVaultAdmin(vault).admin()) revert Errors.AccessDenied(); + + // update deposit data manager + _depositDataManagers[vault] = depositDataManager; + emit DepositDataManagerUpdated(vault, depositDataManager); + } + + /// @inheritdoc IDepositDataManager + function setDepositDataRoot(address vault, bytes32 depositDataRoot) external override { + if (!_vaultsRegistry.vaults(vault)) revert Errors.InvalidVault(); + if (msg.sender != getDepositDataManager(vault)) revert Errors.AccessDenied(); + if (depositDataRoots[vault] == depositDataRoot) revert Errors.ValueNotChanged(); + + depositDataRoots[vault] = depositDataRoot; + // reset validator index on every root update + depositDataIndexes[vault] = 0; + emit DepositDataRootUpdated(vault, depositDataRoot); + } + + /// @inheritdoc IDepositDataManager + function registerValidator( + address vault, + IKeeperValidators.ApprovalParams calldata keeperParams, + bytes32[] calldata proof + ) external override { + if (!_vaultsRegistry.vaults(vault)) revert Errors.InvalidVault(); + + // register validator + IVaultValidators(vault).registerValidators(keeperParams); + + // SLOAD to memory + uint256 currentIndex = depositDataIndexes[vault]; + bytes32 depositDataRoot = depositDataRoots[vault]; + + // check matches merkle root and next validator index + if ( + !MerkleProof.verifyCalldata( + proof, + depositDataRoot, + keccak256(bytes.concat(keccak256(abi.encode(keeperParams.validators, currentIndex)))) + ) + ) { + revert Errors.InvalidProof(); + } + + // increment index for the next validator + unchecked { + // cannot realistically overflow + depositDataIndexes[vault] = currentIndex + 1; + } + } + + /// @inheritdoc IDepositDataManager + function registerValidators( + address vault, + IKeeperValidators.ApprovalParams calldata keeperParams, + uint256[] calldata indexes, + bool[] calldata proofFlags, + bytes32[] calldata proof + ) external override { + if (!_vaultsRegistry.vaults(vault)) revert Errors.InvalidVault(); + + // register validator + IVaultValidators(vault).registerValidators(keeperParams); + + // SLOAD to memory + uint256 currentIndex = depositDataIndexes[vault]; + bytes32 depositDataRoot = depositDataRoots[vault]; + + // define leaves for multiproof + uint256 validatorsCount = indexes.length; + if (validatorsCount == 0) revert Errors.InvalidValidators(); + bytes32[] memory leaves = new bytes32[](validatorsCount); + + // calculate validator length + uint256 validatorLength = keeperParams.validators.length / validatorsCount; + if (validatorLength == 0) revert Errors.InvalidValidators(); + + // calculate leaves + { + uint256 startIndex; + uint256 endIndex; + for (uint256 i = 0; i < validatorsCount; ) { + endIndex += validatorLength; + leaves[indexes[i]] = keccak256( + bytes.concat( + keccak256(abi.encode(keeperParams.validators[startIndex:endIndex], currentIndex)) + ) + ); + + startIndex = endIndex; + unchecked { + // cannot realistically overflow + ++currentIndex; + ++i; + } + } + } + + // check matches merkle root and next validator index + if (!MerkleProof.multiProofVerifyCalldata(proof, proofFlags, depositDataRoot, leaves)) { + revert Errors.InvalidProof(); + } + + // increment index for the next validator + depositDataIndexes[vault] = currentIndex; + } + + /// @inheritdoc IDepositDataManager + function migrate( + bytes32 depositDataRoot, + uint256 validatorIndex, + address depositDataManager + ) external override { + if (!_vaultsRegistry.vaults(msg.sender) || _migrated[msg.sender]) { + revert Errors.AccessDenied(); + } + depositDataRoots[msg.sender] = depositDataRoot; + depositDataIndexes[msg.sender] = validatorIndex; + _depositDataManagers[msg.sender] = depositDataManager; + + // only allow migration once + _migrated[msg.sender] = true; + emit DepositDataMigrated(msg.sender, depositDataRoot, validatorIndex, depositDataManager); + } +} diff --git a/contracts/mocks/EthPrivVaultV3Mock.sol b/contracts/mocks/EthPrivVaultV3Mock.sol index 32a7df19..14f572d1 100644 --- a/contracts/mocks/EthPrivVaultV3Mock.sol +++ b/contracts/mocks/EthPrivVaultV3Mock.sol @@ -15,6 +15,7 @@ contract EthPrivVaultV3Mock is EthPrivVault { address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthPrivVault( @@ -24,6 +25,7 @@ contract EthPrivVaultV3Mock is EthPrivVault { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/mocks/EthVaultMock.sol b/contracts/mocks/EthVaultMock.sol index 693af617..2ee95491 100644 --- a/contracts/mocks/EthVaultMock.sol +++ b/contracts/mocks/EthVaultMock.sol @@ -26,6 +26,7 @@ contract EthVaultMock is EthVault { address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthVault( @@ -35,6 +36,7 @@ contract EthVaultMock is EthVault { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/mocks/EthVaultV3Mock.sol b/contracts/mocks/EthVaultV3Mock.sol index 5e816610..54650dd0 100644 --- a/contracts/mocks/EthVaultV3Mock.sol +++ b/contracts/mocks/EthVaultV3Mock.sol @@ -15,6 +15,7 @@ contract EthVaultV3Mock is EthVault { address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthVault( @@ -24,6 +25,7 @@ contract EthVaultV3Mock is EthVault { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/mocks/EthVaultV4Mock.sol b/contracts/mocks/EthVaultV4Mock.sol index 085c687c..d73e8211 100644 --- a/contracts/mocks/EthVaultV4Mock.sol +++ b/contracts/mocks/EthVaultV4Mock.sol @@ -13,6 +13,7 @@ contract EthVaultV4Mock is EthVaultV3Mock { address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthVaultV3Mock( @@ -22,6 +23,7 @@ contract EthVaultV4Mock is EthVaultV3Mock { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol b/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol index 9d505f76..9ae63750 100644 --- a/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol +++ b/contracts/vaults/ethereum/EthBlocklistErc20Vault.sol @@ -36,6 +36,7 @@ contract EthBlocklistErc20Vault is * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -46,6 +47,7 @@ contract EthBlocklistErc20Vault is address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthErc20Vault( @@ -55,6 +57,7 @@ contract EthBlocklistErc20Vault is osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} @@ -72,7 +75,6 @@ contract EthBlocklistErc20Vault is ); // blocklist manager is initially set to admin address __VaultBlocklist_init(_admin); - __EthErc20Vault_initV2(); } /// @inheritdoc IVaultEthStaking diff --git a/contracts/vaults/ethereum/EthBlocklistVault.sol b/contracts/vaults/ethereum/EthBlocklistVault.sol index a1c84758..b25c0ee3 100644 --- a/contracts/vaults/ethereum/EthBlocklistVault.sol +++ b/contracts/vaults/ethereum/EthBlocklistVault.sol @@ -30,6 +30,7 @@ contract EthBlocklistVault is Initializable, EthVault, VaultBlocklist, IEthBlock * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -40,6 +41,7 @@ contract EthBlocklistVault is Initializable, EthVault, VaultBlocklist, IEthBlock address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthVault( @@ -49,6 +51,7 @@ contract EthBlocklistVault is Initializable, EthVault, VaultBlocklist, IEthBlock osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} @@ -66,7 +69,6 @@ contract EthBlocklistVault is Initializable, EthVault, VaultBlocklist, IEthBlock ); // blocklist manager is initially set to admin address __VaultBlocklist_init(_admin); - __EthVault_initV2(); } /// @inheritdoc IVaultEthStaking diff --git a/contracts/vaults/ethereum/EthErc20Vault.sol b/contracts/vaults/ethereum/EthErc20Vault.sol index 52a1a02f..b7058dee 100644 --- a/contracts/vaults/ethereum/EthErc20Vault.sol +++ b/contracts/vaults/ethereum/EthErc20Vault.sol @@ -53,6 +53,7 @@ contract EthErc20Vault is * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -63,9 +64,11 @@ contract EthErc20Vault is address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) VaultImmutables(_keeper, _vaultsRegistry, _validatorsRegistry) + VaultValidators(depositDataManager) VaultEnterExit(exitingAssetsClaimDelay) VaultOsToken(osTokenVaultController, osTokenConfig) VaultMev(sharedMevEscrow) @@ -186,6 +189,7 @@ contract EthErc20Vault is */ function __EthErc20Vault_initV2() internal onlyInitializing { __VaultState_initV2(); + __VaultValidators_initV2(); } /** diff --git a/contracts/vaults/ethereum/EthGenesisVault.sol b/contracts/vaults/ethereum/EthGenesisVault.sol index 4ea47010..52746796 100644 --- a/contracts/vaults/ethereum/EthGenesisVault.sol +++ b/contracts/vaults/ethereum/EthGenesisVault.sol @@ -44,6 +44,7 @@ contract EthGenesisVault is Initializable, EthVault, IEthGenesisVault { * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param poolEscrow The address of the pool escrow from StakeWise v2 * @param rewardEthToken The address of the rETH2 token from StakeWise v2 * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking @@ -56,6 +57,7 @@ contract EthGenesisVault is Initializable, EthVault, IEthGenesisVault { address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, address poolEscrow, address rewardEthToken, uint256 exitingAssetsClaimDelay @@ -67,6 +69,7 @@ contract EthGenesisVault is Initializable, EthVault, IEthGenesisVault { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) { @@ -239,11 +242,10 @@ contract EthGenesisVault is Initializable, EthVault, IEthGenesisVault { /// @inheritdoc VaultValidators function _registerMultipleValidators( - bytes calldata validators, - uint256[] calldata indexes - ) internal virtual override(VaultValidators, VaultEthStaking) returns (bytes32[] memory leaves) { + bytes calldata validators + ) internal virtual override(VaultValidators, VaultEthStaking) { _pullWithdrawals(); - return super._registerMultipleValidators(validators, indexes); + return super._registerMultipleValidators(validators); } /** diff --git a/contracts/vaults/ethereum/EthPrivErc20Vault.sol b/contracts/vaults/ethereum/EthPrivErc20Vault.sol index 2577c287..6f467dea 100644 --- a/contracts/vaults/ethereum/EthPrivErc20Vault.sol +++ b/contracts/vaults/ethereum/EthPrivErc20Vault.sol @@ -31,6 +31,7 @@ contract EthPrivErc20Vault is Initializable, EthErc20Vault, VaultWhitelist, IEth * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -41,6 +42,7 @@ contract EthPrivErc20Vault is Initializable, EthErc20Vault, VaultWhitelist, IEth address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthErc20Vault( @@ -50,6 +52,7 @@ contract EthPrivErc20Vault is Initializable, EthErc20Vault, VaultWhitelist, IEth osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/vaults/ethereum/EthPrivVault.sol b/contracts/vaults/ethereum/EthPrivVault.sol index 1652a4fd..ec05b2f9 100644 --- a/contracts/vaults/ethereum/EthPrivVault.sol +++ b/contracts/vaults/ethereum/EthPrivVault.sol @@ -30,6 +30,7 @@ contract EthPrivVault is Initializable, EthVault, VaultWhitelist, IEthPrivVault * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -40,6 +41,7 @@ contract EthPrivVault is Initializable, EthVault, VaultWhitelist, IEthPrivVault address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) EthVault( @@ -49,6 +51,7 @@ contract EthPrivVault is Initializable, EthVault, VaultWhitelist, IEthPrivVault osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, exitingAssetsClaimDelay ) {} diff --git a/contracts/vaults/ethereum/EthVault.sol b/contracts/vaults/ethereum/EthVault.sol index 68689eca..da041dc4 100644 --- a/contracts/vaults/ethereum/EthVault.sol +++ b/contracts/vaults/ethereum/EthVault.sol @@ -49,6 +49,7 @@ contract EthVault is * @param osTokenVaultController The address of the OsTokenVaultController contract * @param osTokenConfig The address of the OsTokenConfig contract * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -59,9 +60,11 @@ contract EthVault is address osTokenVaultController, address osTokenConfig, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) VaultImmutables(_keeper, _vaultsRegistry, _validatorsRegistry) + VaultValidators(depositDataManager) VaultEnterExit(exitingAssetsClaimDelay) VaultOsToken(osTokenVaultController, osTokenConfig) VaultMev(sharedMevEscrow) @@ -134,6 +137,7 @@ contract EthVault is */ function __EthVault_initV2() internal onlyInitializing { __VaultState_initV2(); + __VaultValidators_initV2(); } /** diff --git a/contracts/vaults/ethereum/custom/EthFoxVault.sol b/contracts/vaults/ethereum/custom/EthFoxVault.sol index 6023e038..296a311f 100644 --- a/contracts/vaults/ethereum/custom/EthFoxVault.sol +++ b/contracts/vaults/ethereum/custom/EthFoxVault.sol @@ -46,6 +46,7 @@ contract EthFoxVault is * @param _vaultsRegistry The address of the VaultsRegistry contract * @param _validatorsRegistry The contract address used for registering validators in beacon chain * @param sharedMevEscrow The address of the shared MEV escrow + * @param depositDataManager The address of the DepositDataManager contract * @param exitingAssetsClaimDelay The delay after which the assets can be claimed after exiting from staking */ /// @custom:oz-upgrades-unsafe-allow constructor @@ -54,9 +55,11 @@ contract EthFoxVault is address _vaultsRegistry, address _validatorsRegistry, address sharedMevEscrow, + address depositDataManager, uint256 exitingAssetsClaimDelay ) VaultImmutables(_keeper, _vaultsRegistry, _validatorsRegistry) + VaultValidators(depositDataManager) VaultEnterExit(exitingAssetsClaimDelay) VaultMev(sharedMevEscrow) { @@ -147,6 +150,7 @@ contract EthFoxVault is */ function __EthFoxVault_initV2() internal onlyInitializing { __VaultState_initV2(); + __VaultValidators_initV2(); } /** diff --git a/contracts/vaults/modules/VaultEthStaking.sol b/contracts/vaults/modules/VaultEthStaking.sol index 45a35d9d..bacee358 100644 --- a/contracts/vaults/modules/VaultEthStaking.sol +++ b/contracts/vaults/modules/VaultEthStaking.sol @@ -67,52 +67,38 @@ abstract contract VaultEthStaking is publicKey, _withdrawalCredentials(), validator[48:144], - bytes32(validator[144:_validatorLength]) + bytes32(validator[144:validator.length]) ); - emit ValidatorRegistered(publicKey); } /// @inheritdoc VaultValidators - function _registerMultipleValidators( - bytes calldata validators, - uint256[] calldata indexes - ) internal virtual override returns (bytes32[] memory leaves) { - // SLOAD to memory - uint256 currentValIndex = validatorIndex; - + function _registerMultipleValidators(bytes calldata validators) internal virtual override { uint256 startIndex; uint256 endIndex; + uint256 validatorsCount = validators.length / _validatorLength(); + bytes memory withdrawalCredentials = _withdrawalCredentials(); bytes calldata validator; bytes calldata publicKey; - uint256 validatorsCount = indexes.length; - leaves = new bytes32[](validatorsCount); - uint256 validatorDeposit = _validatorDeposit(); - bytes memory withdrawalCreds = _withdrawalCredentials(); - - for (uint256 i = 0; i < validatorsCount; i++) { + for (uint256 i = 0; i < validatorsCount; ) { unchecked { // cannot realistically overflow - endIndex += _validatorLength; + endIndex += _validatorLength(); } validator = validators[startIndex:endIndex]; - leaves[indexes[i]] = keccak256( - bytes.concat(keccak256(abi.encode(validator, currentValIndex))) - ); publicKey = validator[:48]; - // slither-disable-next-line arbitrary-send-eth - IEthValidatorsRegistry(_validatorsRegistry).deposit{value: validatorDeposit}( + IEthValidatorsRegistry(_validatorsRegistry).deposit{value: _validatorDeposit()}( publicKey, - withdrawalCreds, + withdrawalCredentials, validator[48:144], - bytes32(validator[144:_validatorLength]) + bytes32(validator[144:_validatorLength()]) ); + emit ValidatorRegistered(publicKey); startIndex = endIndex; unchecked { // cannot realistically overflow - ++currentValIndex; + ++i; } - emit ValidatorRegistered(publicKey); } } @@ -130,10 +116,23 @@ abstract contract VaultEthStaking is } /// @inheritdoc VaultValidators - function _validatorDeposit() internal pure override returns (uint256) { + function _validatorLength() internal pure virtual override returns (uint256) { + return 176; + } + + /// @inheritdoc VaultValidators + function _validatorDeposit() internal pure virtual override returns (uint256) { return 32 ether; } + /** + * @dev Internal function for calculating Vault withdrawal credentials + * @return The credentials used for the validators withdrawals + */ + function _withdrawalCredentials() internal view virtual returns (bytes memory) { + return abi.encodePacked(bytes1(0x01), bytes11(0x0), address(this)); + } + /** * @dev Initializes the VaultEthStaking contract */ diff --git a/contracts/vaults/modules/VaultState.sol b/contracts/vaults/modules/VaultState.sol index 8fc26f0d..f945fe80 100644 --- a/contracts/vaults/modules/VaultState.sol +++ b/contracts/vaults/modules/VaultState.sol @@ -24,10 +24,10 @@ abstract contract VaultState is VaultImmutables, Initializable, VaultFee, IVault uint128 internal _totalAssets; /// @inheritdoc IVaultState - uint128 public override queuedShares; - uint128 internal _unclaimedAssets; + uint128 public override queuedShares; // deprecated + uint128 internal _unclaimedAssets; // deprecated + ExitQueue.History internal _exitQueue; // deprecated - ExitQueue.History internal _exitQueue; mapping(bytes32 => uint256) internal _exitRequests; mapping(address => uint256) internal _balances; diff --git a/contracts/vaults/modules/VaultValidators.sol b/contracts/vaults/modules/VaultValidators.sol index 8a54a73a..ebf220f5 100644 --- a/contracts/vaults/modules/VaultValidators.sol +++ b/contracts/vaults/modules/VaultValidators.sol @@ -5,6 +5,7 @@ pragma solidity =0.8.22; import {Initializable} from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; import {MerkleProof} from '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol'; import {IKeeperValidators} from '../../interfaces/IKeeperValidators.sol'; +import {IDepositDataManager} from '../../interfaces/IDepositDataManager.sol'; import {IVaultValidators} from '../../interfaces/IVaultValidators.sol'; import {Errors} from '../../libraries/Errors.sol'; import {VaultImmutables} from './VaultImmutables.sol'; @@ -23,160 +24,99 @@ abstract contract VaultValidators is VaultState, IVaultValidators { - uint256 internal constant _validatorLength = 176; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address private immutable _depositDataManager; - /// @inheritdoc IVaultValidators - bytes32 public override validatorsRoot; + /// deprecated. Deposit data management is moved to DepositDataManager contract + bytes32 private _validatorsRoot; - /// @inheritdoc IVaultValidators - uint256 public override validatorIndex; + /// deprecated. Deposit data management is moved to DepositDataManager contract + uint256 private _validatorIndex; address private _keysManager; + /** + * @dev Constructor + * @dev Since the immutable variable value is stored in the bytecode, + * its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + * @param depositDataManager The address of the deposit data manager contract + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address depositDataManager) { + _depositDataManager = depositDataManager; + } + /// @inheritdoc IVaultValidators function keysManager() public view override returns (address) { // SLOAD to memory address keysManager_ = _keysManager; - // if keysManager is not set, use admin address - return keysManager_ == address(0) ? admin : keysManager_; + // if keysManager is not set, use deposit data manager contract address + return keysManager_ == address(0) ? _depositDataManager : keysManager_; } /// @inheritdoc IVaultValidators - function registerValidator( - IKeeperValidators.ApprovalParams calldata keeperParams, - bytes32[] calldata proof + function registerValidators( + IKeeperValidators.ApprovalParams calldata keeperParams ) external override { - _checkHarvested(); - // get approval from oracles IKeeperValidators(_keeper).approveValidators(keeperParams); - // check enough withdrawable assets - if (withdrawableAssets() < _validatorDeposit()) revert Errors.InsufficientAssets(); - - // check validator length is valid - if (keeperParams.validators.length != _validatorLength) revert Errors.InvalidValidator(); - - // SLOAD to memory - uint256 currentIndex = validatorIndex; - - // check matches merkle root and next validator index - if ( - !MerkleProof.verifyCalldata( - proof, - validatorsRoot, - keccak256(bytes.concat(keccak256(abi.encode(keeperParams.validators, currentIndex)))) - ) - ) { - revert Errors.InvalidProof(); - } - - // register validator - _registerSingleValidator(keeperParams.validators); - - // increment index for the next validator - unchecked { - // cannot realistically overflow - validatorIndex = currentIndex + 1; - } - } - - /// @inheritdoc IVaultValidators - function registerValidators( - IKeeperValidators.ApprovalParams calldata keeperParams, - uint256[] calldata indexes, - bool[] calldata proofFlags, - bytes32[] calldata proof - ) external override { + // check vault is up to date _checkHarvested(); - // get approval from oracles - IKeeperValidators(_keeper).approveValidators(keeperParams); - - // check enough withdrawable assets - uint256 validatorsCount = indexes.length; - if (withdrawableAssets() < _validatorDeposit() * validatorsCount) { - revert Errors.InsufficientAssets(); - } + // check access + if (msg.sender != keysManager()) revert Errors.AccessDenied(); // check validators length is valid + uint256 validatorLength = _validatorLength(); + uint256 validatorsCount = keeperParams.validators.length / validatorLength; unchecked { if ( - validatorsCount == 0 || validatorsCount * _validatorLength != keeperParams.validators.length + validatorsCount == 0 || validatorsCount * validatorLength != keeperParams.validators.length ) { revert Errors.InvalidValidators(); } } - // check matches merkle root and next validator index - if ( - !MerkleProof.multiProofVerifyCalldata( - proof, - proofFlags, - validatorsRoot, - _registerMultipleValidators(keeperParams.validators, indexes) - ) - ) { - revert Errors.InvalidProof(); + // check enough withdrawable assets + if (withdrawableAssets() < _validatorDeposit() * validatorsCount) { + revert Errors.InsufficientAssets(); } - // increment index for the next validator - unchecked { - // cannot realistically overflow - validatorIndex += validatorsCount; + if (keeperParams.validators.length == validatorLength) { + // register single validator + _registerSingleValidator(keeperParams.validators); + } else { + // register multiple validators + _registerMultipleValidators(keeperParams.validators); } } /// @inheritdoc IVaultValidators function setKeysManager(address keysManager_) external override { _checkAdmin(); - if (keysManager_ == address(0)) revert Errors.ZeroAddress(); // update keysManager address _keysManager = keysManager_; emit KeysManagerUpdated(msg.sender, keysManager_); } - /// @inheritdoc IVaultValidators - function setValidatorsRoot(bytes32 _validatorsRoot) external override { - if (msg.sender != keysManager()) revert Errors.AccessDenied(); - _setValidatorsRoot(_validatorsRoot); - } - /** - * @dev Internal function for updating the validators root externally or from the initializer - * @param _validatorsRoot The new validators merkle tree root + * @dev Internal function for registering validator. Must emit ValidatorRegistered event. + * @param validator The validator registration data */ - function _setValidatorsRoot(bytes32 _validatorsRoot) private { - validatorsRoot = _validatorsRoot; - // reset validator index on every root update - validatorIndex = 0; - emit ValidatorsRootUpdated(msg.sender, _validatorsRoot); - } + function _registerSingleValidator(bytes calldata validator) internal virtual; /** - * @dev Internal function for calculating Vault withdrawal credentials - * @return The credentials used for the validators withdrawals + * @dev Internal function for registering multiple validators. Must emit ValidatorRegistered event for every validator. + * @param validators The validators registration data */ - function _withdrawalCredentials() internal view returns (bytes memory) { - return abi.encodePacked(bytes1(0x01), bytes11(0x0), address(this)); - } + function _registerMultipleValidators(bytes calldata validators) internal virtual; /** - * @dev Internal function for registering single validator. Must emit ValidatorRegistered event. - * @param validator The concatenation of the validator public key, signature and deposit data root + * @dev Internal function for defining the length of the validator data + * @return The length of the single validator data */ - function _registerSingleValidator(bytes calldata validator) internal virtual; - - /** - * @dev Internal function for registering multiple validators. Must emit ValidatorRegistered event for every validator. - * @param validators The concatenation of the validators' public key, signature and deposit data root - * @param indexes The indexes of the leaves for the merkle tree multi proof verification - * @return leaves The leaves used for the merkle tree multi proof verification - */ - function _registerMultipleValidators( - bytes calldata validators, - uint256[] calldata indexes - ) internal virtual returns (bytes32[] memory leaves); + function _validatorLength() internal pure virtual returns (uint256); /** * @dev Internal function for fetching validator deposit amount @@ -191,6 +131,22 @@ abstract contract VaultValidators is if (capacity() < _validatorDeposit()) revert Errors.InvalidCapacity(); } + /** + * @dev Initializes the V2 of the VaultValidators contract + */ + function __VaultValidators_initV2() internal onlyInitializing { + IDepositDataManager(_depositDataManager).migrate( + _validatorsRoot, + _validatorIndex, + _keysManager + ); + + // clean up variables + delete _validatorsRoot; + delete _validatorIndex; + delete _keysManager; + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/test/DepositDataManager.spec.ts b/test/DepositDataManager.spec.ts new file mode 100644 index 00000000..f496bd23 --- /dev/null +++ b/test/DepositDataManager.spec.ts @@ -0,0 +1,424 @@ +import { ethers } from 'hardhat' +import { Contract, Signer, Wallet } from 'ethers' +import { UintNumberType } from '@chainsafe/ssz' +import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' +import { ThenArg } from '../helpers/types' +import { EthVault, IKeeperValidators, Keeper, DepositDataManager } from '../typechain-types' +import snapshotGasCost from './shared/snapshotGasCost' +import { expect } from './shared/expect' +import { setBalance, toHexString } from './shared/utils' +import { + createEthValidatorsData, + EthValidatorsData, + exitSignatureIpfsHashes, + getEthValidatorsSigningData, + getValidatorProof, + getValidatorsMultiProof, + getWithdrawalCredentials, + ValidatorsMultiProof, +} from './shared/validators' +import { ethVaultFixture, getOraclesSignatures } from './shared/fixtures' +import { + MAX_UINT256, + PANIC_CODES, + VALIDATORS_DEADLINE, + VALIDATORS_MIN_ORACLES, + ZERO_ADDRESS, + ZERO_BYTES32, +} from './shared/constants' + +const gwei = 1000000000n +const uintSerializer = new UintNumberType(8) + +describe('DepositDataManager', () => { + const validatorDeposit = ethers.parseEther('32') + const capacity = MAX_UINT256 + const feePercent = 1000 + const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' + const deadline = VALIDATORS_DEADLINE + + let admin: Signer, other: Wallet, manager: Wallet, dao: Wallet + let vault: EthVault, + keeper: Keeper, + validatorsRegistry: Contract, + vaultsRegistry: VaultRegistry, + depositDataManager: DepositDataManager + let validatorsData: EthValidatorsData + let validatorsRegistryRoot: string + + let createVault: ThenArg>['createEthVault'] + + before('create fixture loader', async () => { + ;[dao, admin, other, manager] = await (ethers as any).getSigners() + }) + + beforeEach('deploy fixture', async () => { + ;({ + validatorsRegistry, + createEthVault: createVault, + keeper, + depositDataManager, + vaultsRegistry, + } = await loadFixture(ethVaultFixture)) + + vault = await createVault(admin, { + capacity, + feePercent, + metadataIpfsHash, + }) + admin = await ethers.getImpersonatedSigner(await vault.admin()) + validatorsData = await createEthValidatorsData(vault) + validatorsRegistryRoot = await validatorsRegistry.get_deposit_root() + await vault.connect(other).deposit(other.address, ZERO_ADDRESS, { value: validatorDeposit }) + }) + + describe('deposit data manager update', () => { + it('fails for non-vault', async () => { + await expect( + depositDataManager.connect(admin).setDepositDataManager(other.address, manager.address) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidVault') + }) + + it('fails for non-admin', async () => { + await expect( + depositDataManager + .connect(other) + .setDepositDataManager(await vault.getAddress(), manager.address) + ).to.be.revertedWithCustomError(depositDataManager, 'AccessDenied') + }) + + it('succeeds', async () => { + const vaultAddr = await vault.getAddress() + const adminAddr = await admin.getAddress() + expect(await depositDataManager.getDepositDataManager(vaultAddr)).to.eq(adminAddr) + const receipt = await depositDataManager + .connect(admin) + .setDepositDataManager(vaultAddr, manager.address) + await expect(receipt) + .to.emit(depositDataManager, 'DepositDataManagerUpdated') + .withArgs(vaultAddr, manager.address) + expect(await depositDataManager.getDepositDataManager(vaultAddr)).to.eq(manager.address) + await snapshotGasCost(receipt) + }) + }) + + describe('deposit data root update', () => { + beforeEach('set manager', async () => { + await depositDataManager + .connect(admin) + .setDepositDataManager(await vault.getAddress(), manager.address) + }) + + it('fails for invalid vault', async () => { + await expect( + depositDataManager.connect(manager).setDepositDataRoot(other.address, validatorsData.root) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidVault') + }) + + it('fails from non-manager', async () => { + await expect( + depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), validatorsData.root) + ).to.be.revertedWithCustomError(depositDataManager, 'AccessDenied') + }) + + it('fails for same root', async () => { + const vaultAddr = await vault.getAddress() + await depositDataManager.connect(manager).setDepositDataRoot(vaultAddr, validatorsData.root) + await expect( + depositDataManager.connect(manager).setDepositDataRoot(vaultAddr, validatorsData.root) + ).to.be.revertedWithCustomError(depositDataManager, 'ValueNotChanged') + }) + + it('success', async () => { + const vaultAddr = await vault.getAddress() + const receipt = await depositDataManager + .connect(manager) + .setDepositDataRoot(vaultAddr, validatorsData.root) + await expect(receipt) + .to.emit(depositDataManager, 'DepositDataRootUpdated') + .withArgs(vaultAddr, validatorsData.root) + await snapshotGasCost(receipt) + }) + }) + + describe('single validator', () => { + let validator: Buffer + let proof: string[] + let approvalParams: IKeeperValidators.ApprovalParamsStruct + + beforeEach(async () => { + validator = validatorsData.validators[0] + proof = getValidatorProof(validatorsData.tree, validator, 0) + await depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), validatorsData.root) + const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] + const signatures = getOraclesSignatures( + await getEthValidatorsSigningData( + validator, + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot + ), + VALIDATORS_MIN_ORACLES + ) + approvalParams = { + validatorsRegistryRoot, + validators: validator, + signatures, + exitSignaturesIpfsHash, + deadline, + } + }) + + it('fails for invalid vault', async () => { + await expect( + depositDataManager.registerValidator(other.address, approvalParams, proof) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidVault') + }) + + it('fails with invalid proof', async () => { + const invalidProof = getValidatorProof(validatorsData.tree, validatorsData.validators[1], 1) + await expect( + depositDataManager.registerValidator(await vault.getAddress(), approvalParams, invalidProof) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidProof') + }) + + it('succeeds', async () => { + const index = await validatorsRegistry.get_deposit_count() + const receipt = await depositDataManager.registerValidator( + await vault.getAddress(), + approvalParams, + proof + ) + const publicKey = `0x${validator.subarray(0, 48).toString('hex')}` + await expect(receipt).to.emit(vault, 'ValidatorRegistered').withArgs(publicKey) + await expect(receipt) + .to.emit(validatorsRegistry, 'DepositEvent') + .withArgs( + publicKey, + toHexString(getWithdrawalCredentials(await vault.getAddress())), + toHexString(Buffer.from(uintSerializer.serialize(Number(validatorDeposit / gwei)))), + toHexString(validator.subarray(48, 144)), + index + ) + expect(await depositDataManager.depositDataIndexes(await vault.getAddress())).to.eq(1) + + await depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), ZERO_BYTES32) + expect(await depositDataManager.depositDataIndexes(await vault.getAddress())).to.eq(0) + await snapshotGasCost(receipt) + }) + }) + + describe('multiple validators', () => { + let validators: Buffer[] + let indexes: number[] + let approvalParams: IKeeperValidators.ApprovalParamsStruct + let multiProof: ValidatorsMultiProof + let signatures: Buffer + + beforeEach(async () => { + multiProof = getValidatorsMultiProof(validatorsData.tree, validatorsData.validators, [ + ...Array(validatorsData.validators.length).keys(), + ]) + validators = validatorsData.validators + const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] + const sortedVals = multiProof.leaves.map((v) => v[0]) + const vaultAddr = await vault.getAddress() + await depositDataManager.connect(admin).setDepositDataRoot(vaultAddr, validatorsData.root) + indexes = validators.map((v) => sortedVals.indexOf(v)) + const balance = + validatorDeposit * BigInt(validators.length) + + (await vault.totalExitingAssets()) + + (await ethers.provider.getBalance(vaultAddr)) + await setBalance(vaultAddr, balance) + signatures = getOraclesSignatures( + await getEthValidatorsSigningData( + Buffer.concat(validators), + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot + ), + VALIDATORS_MIN_ORACLES + ) + approvalParams = { + validatorsRegistryRoot, + validators: Buffer.concat(validators), + signatures, + exitSignaturesIpfsHash, + deadline, + } + }) + + it('fails for invalid vault', async () => { + await expect( + depositDataManager.registerValidators( + other.address, + approvalParams, + indexes, + multiProof.proofFlags, + multiProof.proof + ) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidVault') + }) + + it('fails with invalid validators count', async () => { + const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] + await expect( + depositDataManager.registerValidators( + await vault.getAddress(), + { + validatorsRegistryRoot, + validators: Buffer.from(''), + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + Buffer.from(''), + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot + ), + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }, + indexes, + multiProof.proofFlags, + multiProof.proof + ) + ).to.be.revertedWithCustomError(vault, 'InvalidValidators') + }) + + it('fails with invalid proof', async () => { + const invalidMultiProof = getValidatorsMultiProof( + validatorsData.tree, + validators.slice(1), + [...Array(validatorsData.validators.length).keys()].slice(1) + ) + + await expect( + depositDataManager.registerValidators( + await vault.getAddress(), + approvalParams, + indexes, + invalidMultiProof.proofFlags, + invalidMultiProof.proof + ) + ).to.be.revertedWithCustomError(depositDataManager, 'MerkleProofInvalidMultiproof') + }) + + it('fails with invalid indexes', async () => { + const vaultAddr = await vault.getAddress() + await expect( + depositDataManager.registerValidators( + vaultAddr, + approvalParams, + [], + multiProof.proofFlags, + multiProof.proof + ) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidValidators') + + await expect( + depositDataManager.registerValidators( + vaultAddr, + approvalParams, + indexes.map((i) => i + 1), + multiProof.proofFlags, + multiProof.proof + ) + ).to.be.revertedWithPanic(PANIC_CODES.OUT_OF_BOUND_INDEX) + + await expect( + depositDataManager.registerValidators( + vaultAddr, + approvalParams, + indexes.sort(() => 0.5 - Math.random()), + multiProof.proofFlags, + multiProof.proof + ) + ).to.be.revertedWithCustomError(depositDataManager, 'InvalidProof') + }) + + it('succeeds', async () => { + const startIndex = uintSerializer.deserialize( + ethers.getBytes(await validatorsRegistry.get_deposit_count()) + ) + const vaultAddress = await vault.getAddress() + const receipt = await depositDataManager.registerValidators( + vaultAddress, + approvalParams, + indexes, + multiProof.proofFlags, + multiProof.proof + ) + for (let i = 0; i < validators.length; i++) { + const validator = validators[i] + const publicKey = toHexString(validator.subarray(0, 48)) + await expect(receipt).to.emit(vault, 'ValidatorRegistered').withArgs(publicKey) + await expect(receipt) + .to.emit(validatorsRegistry, 'DepositEvent') + .withArgs( + publicKey, + toHexString(getWithdrawalCredentials(await vault.getAddress())), + toHexString(Buffer.from(uintSerializer.serialize(Number(validatorDeposit / gwei)))), + toHexString(validator.subarray(48, 144)), + toHexString(Buffer.from(uintSerializer.serialize(startIndex + i))) + ) + } + expect(await depositDataManager.depositDataIndexes(vaultAddress)).to.eq(validators.length) + await snapshotGasCost(receipt) + }) + }) + + describe('migrate', () => { + const validatorIndex = 2 + + beforeEach('set manager', async () => { + await vaultsRegistry.connect(dao).addVault(other.address) + }) + + it('fails for non-vault', async () => { + await expect( + depositDataManager + .connect(admin) + .migrate(validatorsData.root, validatorIndex, manager.address) + ).to.be.revertedWithCustomError(depositDataManager, 'AccessDenied') + }) + + it('fails for already migrated', async () => { + await depositDataManager + .connect(other) + .migrate(validatorsData.root, validatorIndex, manager.address) + + await expect( + depositDataManager + .connect(other) + .migrate(validatorsData.root, validatorIndex, manager.address) + ).to.be.revertedWithCustomError(depositDataManager, 'AccessDenied') + }) + + it('succeeds', async () => { + const receipt = await depositDataManager + .connect(other) + .migrate(validatorsData.root, validatorIndex, manager.address) + await expect(receipt) + .to.emit(depositDataManager, 'DepositDataMigrated') + .withArgs(other.address, validatorsData.root, validatorIndex, manager.address) + expect(await depositDataManager.getDepositDataManager(other.address)).to.eq(manager.address) + expect(await depositDataManager.depositDataRoots(other.address)).to.eq(validatorsData.root) + expect(await depositDataManager.depositDataIndexes(other.address)).to.eq(validatorIndex) + await snapshotGasCost(receipt) + }) + }) +}) diff --git a/test/EthBlocklistErc20Vault.spec.ts b/test/EthBlocklistErc20Vault.spec.ts index 964da341..9945a8b4 100644 --- a/test/EthBlocklistErc20Vault.spec.ts +++ b/test/EthBlocklistErc20Vault.spec.ts @@ -5,6 +5,7 @@ import { EthBlocklistErc20Vault, IKeeperRewards, Keeper, + DepositDataManager, OsTokenVaultController, } from '../typechain-types' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' @@ -31,7 +32,8 @@ describe('EthBlocklistErc20Vault', () => { let vault: EthBlocklistErc20Vault, keeper: Keeper, validatorsRegistry: Contract, - osTokenVaultController: OsTokenVaultController + osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager beforeEach('deploy fixtures', async () => { ;[sender, admin, other, blocklistManager] = await (ethers as any).getSigners() @@ -47,6 +49,7 @@ describe('EthBlocklistErc20Vault', () => { keeper = fixture.keeper validatorsRegistry = fixture.validatorsRegistry osTokenVaultController = fixture.osTokenVaultController + depositDataManager = fixture.depositDataManager }) it('has id', async () => { @@ -114,7 +117,7 @@ describe('EthBlocklistErc20Vault', () => { }) it('cannot update state and call by blocked sender', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const vaultReward = getHarvestParams(await vault.getAddress(), ethers.parseEther('1'), 0n) const tree = await updateRewards(keeper, [vaultReward]) @@ -184,7 +187,7 @@ describe('EthBlocklistErc20Vault', () => { let osTokenShares: bigint beforeEach(async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(sender).deposit(sender.address, referrer, { value: assets }) osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) }) diff --git a/test/EthBlocklistVault.spec.ts b/test/EthBlocklistVault.spec.ts index 51e93812..ec6f543a 100644 --- a/test/EthBlocklistVault.spec.ts +++ b/test/EthBlocklistVault.spec.ts @@ -6,6 +6,7 @@ import { IKeeperRewards, Keeper, OsTokenVaultController, + DepositDataManager, } from '../typechain-types' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -29,7 +30,8 @@ describe('EthBlocklistVault', () => { let vault: EthBlocklistVault, keeper: Keeper, validatorsRegistry: Contract, - osTokenVaultController: OsTokenVaultController + osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager beforeEach('deploy fixtures', async () => { ;[sender, admin, other, blocklistManager] = await (ethers as any).getSigners() @@ -43,6 +45,7 @@ describe('EthBlocklistVault', () => { keeper = fixture.keeper validatorsRegistry = fixture.validatorsRegistry osTokenVaultController = fixture.osTokenVaultController + depositDataManager = fixture.depositDataManager }) it('has id', async () => { @@ -75,7 +78,7 @@ describe('EthBlocklistVault', () => { }) it('cannot update state and call by blocked sender', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const vaultReward = getHarvestParams(await vault.getAddress(), ethers.parseEther('1'), 0n) const tree = await updateRewards(keeper, [vaultReward]) @@ -145,7 +148,7 @@ describe('EthBlocklistVault', () => { let osTokenShares: bigint beforeEach(async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(sender).deposit(sender.address, referrer, { value: assets }) osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) }) diff --git a/test/EthErc20Vault.spec.ts b/test/EthErc20Vault.spec.ts index 441667d4..9f22b633 100644 --- a/test/EthErc20Vault.spec.ts +++ b/test/EthErc20Vault.spec.ts @@ -8,6 +8,7 @@ import { VaultsRegistry, OsTokenConfig, SharedMevEscrow, + DepositDataManager, } from '../typechain-types' import snapshotGasCost from './shared/snapshotGasCost' import { @@ -44,7 +45,8 @@ describe('EthErc20Vault', () => { osTokenVaultController: OsTokenVaultController, vaultsRegistry: VaultsRegistry, osTokenConfig: OsTokenConfig, - sharedMevEscrow: SharedMevEscrow + sharedMevEscrow: SharedMevEscrow, + depositDataManager: DepositDataManager beforeEach('deploy fixtures', async () => { ;[sender, receiver, admin] = (await (ethers as any).getSigners()).slice(1, 4) @@ -63,6 +65,7 @@ describe('EthErc20Vault', () => { osTokenConfig = fixture.osTokenConfig vaultsRegistry = fixture.vaultsRegistry sharedMevEscrow = fixture.sharedMevEscrow + depositDataManager = fixture.depositDataManager }) it('has id', async () => { @@ -118,7 +121,7 @@ describe('EthErc20Vault', () => { }) it('enter exit queue emits transfer event', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) expect(await vault.totalExitingAssets()).to.be.eq(0) const totalExitingBefore = await vault.totalExitingAssets() const totalAssetsBefore = await vault.totalAssets() @@ -168,7 +171,7 @@ describe('EthErc20Vault', () => { await vault .connect(admin) .deposit(await admin.getAddress(), ZERO_ADDRESS, { value: validatorDeposit }) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, depositDataManager, admin, validatorsRegistry) const rewardsTree = await updateRewards(keeper, [ { vault: await vault.getAddress(), reward: 0n, unlockedMevReward: 0n }, @@ -197,7 +200,7 @@ describe('EthErc20Vault', () => { }) it('cannot transfer vault shares when unharvested and osToken minted', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const assets = ethers.parseEther('1') const shares = await vault.convertToShares(assets) const osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) @@ -217,7 +220,7 @@ describe('EthErc20Vault', () => { }) it('cannot transfer vault shares when LTV is violated', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const assets = ethers.parseEther('2') const shares = await vault.convertToShares(assets) const osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) @@ -234,7 +237,7 @@ describe('EthErc20Vault', () => { }) it('can transfer vault shares when LTV is not violated', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const assets = ethers.parseEther('2') const osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) const transferShares = await vault.convertToShares(ethers.parseEther('0.1')) diff --git a/test/EthFoxVault.spec.ts b/test/EthFoxVault.spec.ts index f1649753..39d61760 100644 --- a/test/EthFoxVault.spec.ts +++ b/test/EthFoxVault.spec.ts @@ -2,7 +2,7 @@ import { ethers } from 'hardhat' import keccak256 from 'keccak256' import { Contract, parseEther, Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { Keeper, IKeeperRewards, EthFoxVault } from '../typechain-types' +import { Keeper, IKeeperRewards, EthFoxVault, DepositDataManager } from '../typechain-types' import { ThenArg } from '../helpers/types' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -22,7 +22,10 @@ describe('EthFoxVault', () => { const referrer = ZERO_ADDRESS const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' let sender: Wallet, blocklistManager: Wallet, admin: Signer, other: Wallet - let vault: EthFoxVault, keeper: Keeper, validatorsRegistry: Contract + let vault: EthFoxVault, + keeper: Keeper, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createFoxVault: ThenArg>['createEthFoxVault'] @@ -35,6 +38,7 @@ describe('EthFoxVault', () => { createEthFoxVault: createFoxVault, keeper, validatorsRegistry, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createFoxVault(admin, { capacity, @@ -127,7 +131,7 @@ describe('EthFoxVault', () => { }) it('cannot update state and call', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const vaultReward = getHarvestParams(await vault.getAddress(), ethers.parseEther('1'), 0n) const tree = await updateRewards(keeper, [vaultReward]) @@ -220,7 +224,7 @@ describe('EthFoxVault', () => { }) it('blocklist manager can eject all of the user assets for collateralized vault', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const tx = await vault.connect(blocklistManager).ejectUser(sender.address) const positionTicket = await extractExitPositionTicket(tx) diff --git a/test/EthGenesisVault.spec.ts b/test/EthGenesisVault.spec.ts index 4ee10c43..83a72967 100644 --- a/test/EthGenesisVault.spec.ts +++ b/test/EthGenesisVault.spec.ts @@ -1,6 +1,12 @@ import { ethers } from 'hardhat' import { Contract, Signer, Wallet } from 'ethers' -import { EthGenesisVault, Keeper, PoolEscrowMock, LegacyRewardTokenMock } from '../typechain-types' +import { + EthGenesisVault, + Keeper, + PoolEscrowMock, + LegacyRewardTokenMock, + DepositDataManager, +} from '../typechain-types' import { createDepositorMock, ethVaultFixture, getOraclesSignatures } from './shared/fixtures' import { expect } from './shared/expect' import keccak256 from 'keccak256' @@ -43,7 +49,10 @@ describe('EthGenesisVault', () => { const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' const deadline = VALIDATORS_DEADLINE let admin: Signer, other: Wallet - let vault: EthGenesisVault, keeper: Keeper, validatorsRegistry: Contract + let vault: EthGenesisVault, + keeper: Keeper, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let poolEscrow: PoolEscrowMock let rewardEthToken: LegacyRewardTokenMock @@ -56,13 +65,7 @@ describe('EthGenesisVault', () => { async function collatEthVault() { if (MAINNET_FORK.enabled) return - await collateralizeEthVault( - vault, - keeper, - validatorsRegistry, - admin, - await poolEscrow.getAddress() - ) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) } beforeEach('deploy fixtures', async () => { @@ -70,6 +73,7 @@ describe('EthGenesisVault', () => { const fixture = await loadFixture(ethVaultFixture) keeper = fixture.keeper validatorsRegistry = fixture.validatorsRegistry + depositDataManager = fixture.depositDataManager ;[vault, rewardEthToken, poolEscrow] = await fixture.createEthGenesisVault(admin, { capacity, feePercent, @@ -278,7 +282,13 @@ describe('EthGenesisVault', () => { await setBalance(vaultAddr, 0n) await setBalance(poolEscrowAddr, validatorDeposit + vaultBalance + poolEscrowBalance) expect(await vault.withdrawableAssets()).to.be.greaterThanOrEqual(validatorDeposit) - const tx = await registerEthValidator(vault, keeper, validatorsRegistry, admin) + const tx = await registerEthValidator( + vault, + keeper, + depositDataManager, + admin, + validatorsRegistry + ) await expect(tx) .to.emit(poolEscrow, 'Withdrawn') .withArgs(vaultAddr, vaultAddr, validatorDeposit + vaultBalance + poolEscrowBalance) @@ -290,7 +300,10 @@ describe('EthGenesisVault', () => { await collatEthVault() const validatorsData = await createEthValidatorsData(vault) const validatorsRegistryRoot = await validatorsRegistry.get_deposit_root() - await vault.connect(admin).setValidatorsRoot(validatorsData.root) + const vaultAddr = await vault.getAddress() + // reset validator index + await depositDataManager.connect(admin).setDepositDataRoot(vaultAddr, ZERO_BYTES32) + await depositDataManager.connect(admin).setDepositDataRoot(vaultAddr, validatorsData.root) const proof = getValidatorsMultiProof(validatorsData.tree, validatorsData.validators, [ ...Array(validatorsData.validators.length).keys(), ]) @@ -318,7 +331,6 @@ describe('EthGenesisVault', () => { deadline, } - const vaultAddr = await vault.getAddress() const vaultBalance = await ethers.provider.getBalance(vaultAddr) const poolEscrowAddr = await poolEscrow.getAddress() const poolEscrowBalance = await ethers.provider.getBalance(poolEscrowAddr) @@ -326,7 +338,9 @@ describe('EthGenesisVault', () => { await setBalance(vaultAddr, 0n) await setBalance(poolEscrowAddr, assets + vaultBalance + poolEscrowBalance) - const tx = await vault.registerValidators(approveParams, indexes, proof.proofFlags, proof.proof) + const tx = await depositDataManager + .connect(admin) + .registerValidators(vaultAddr, approveParams, indexes, proof.proofFlags, proof.proof) await expect(tx) .to.emit(poolEscrow, 'Withdrawn') .withArgs(vaultAddr, vaultAddr, assets + vaultBalance + poolEscrowBalance) diff --git a/test/EthPrivErc20Vault.spec.ts b/test/EthPrivErc20Vault.spec.ts index 52f4fcac..1357f15f 100644 --- a/test/EthPrivErc20Vault.spec.ts +++ b/test/EthPrivErc20Vault.spec.ts @@ -6,6 +6,7 @@ import { IKeeperRewards, Keeper, OsTokenVaultController, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' @@ -26,7 +27,8 @@ describe('EthPrivErc20Vault', () => { let vault: EthPrivErc20Vault, keeper: Keeper, validatorsRegistry: Contract, - osTokenVaultController: OsTokenVaultController + osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager let createPrivateVault: ThenArg>['createEthPrivErc20Vault'] @@ -37,6 +39,7 @@ describe('EthPrivErc20Vault', () => { keeper, validatorsRegistry, osTokenVaultController, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createPrivateVault(admin, { capacity, @@ -71,7 +74,7 @@ describe('EthPrivErc20Vault', () => { }) it('cannot update state and call', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const vaultReward = ethers.parseEther('1') const tree = await updateRewards(keeper, [ { reward: vaultReward, unlockedMevReward: 0n, vault: await vault.getAddress() }, @@ -188,7 +191,7 @@ describe('EthPrivErc20Vault', () => { beforeEach(async () => { await vault.connect(admin).updateWhitelist(sender.address, true) await vault.connect(admin).updateWhitelist(await admin.getAddress(), true) - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(sender).deposit(sender.address, referrer, { value: assets }) osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) }) diff --git a/test/EthPrivVault.spec.ts b/test/EthPrivVault.spec.ts index 746b6c33..c0d46ecf 100644 --- a/test/EthPrivVault.spec.ts +++ b/test/EthPrivVault.spec.ts @@ -1,7 +1,12 @@ import { ethers } from 'hardhat' import { Contract, Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthPrivVault, Keeper, OsTokenVaultController } from '../typechain-types' +import { + EthPrivVault, + Keeper, + OsTokenVaultController, + DepositDataManager, +} from '../typechain-types' import { ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' import { ZERO_ADDRESS } from './shared/constants' @@ -18,7 +23,8 @@ describe('EthPrivVault', () => { let vault: EthPrivVault, keeper: Keeper, validatorsRegistry: Contract, - osTokenVaultController: OsTokenVaultController + osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager beforeEach('deploy fixtures', async () => { ;[sender, admin, other] = await (ethers as any).getSigners() @@ -32,6 +38,7 @@ describe('EthPrivVault', () => { keeper = fixture.keeper validatorsRegistry = fixture.validatorsRegistry osTokenVaultController = fixture.osTokenVaultController + depositDataManager = fixture.depositDataManager }) it('has id', async () => { @@ -55,7 +62,7 @@ describe('EthPrivVault', () => { beforeEach(async () => { await vault.connect(admin).updateWhitelist(await admin.getAddress(), true) - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(admin).updateWhitelist(sender.address, true) await vault.connect(sender).deposit(sender.address, referrer, { value: assets }) osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) diff --git a/test/EthVault.burn.spec.ts b/test/EthVault.burn.spec.ts index 6f54e51b..4be174d9 100644 --- a/test/EthVault.burn.spec.ts +++ b/test/EthVault.burn.spec.ts @@ -1,7 +1,13 @@ import { ethers } from 'hardhat' import { Contract, ContractTransactionReceipt, Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthVault, Keeper, OsTokenVaultController, OsToken } from '../typechain-types' +import { + EthVault, + Keeper, + OsTokenVaultController, + OsToken, + DepositDataManager, +} from '../typechain-types' import { ThenArg } from '../helpers/types' import snapshotGasCost from './shared/snapshotGasCost' import { createUnknownVaultMock, ethVaultFixture } from './shared/fixtures' @@ -24,7 +30,8 @@ describe('EthVault - burn', () => { keeper: Keeper, osTokenVaultController: OsTokenVaultController, osToken: OsToken, - validatorsRegistry: Contract + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] @@ -36,12 +43,13 @@ describe('EthVault - burn', () => { validatorsRegistry, osToken, osTokenVaultController, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault(admin, vaultParams) admin = await ethers.getImpersonatedSigner(await vault.admin()) // collateralize vault - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(sender).deposit(sender.address, ZERO_ADDRESS, { value: assets }) osTokenShares = await osTokenVaultController.convertToShares(osTokenAssets) await vault.connect(sender).mintOsToken(sender.address, osTokenShares, ZERO_ADDRESS) diff --git a/test/EthVault.deposit.spec.ts b/test/EthVault.deposit.spec.ts index de7e52f3..39fc11b1 100644 --- a/test/EthVault.deposit.spec.ts +++ b/test/EthVault.deposit.spec.ts @@ -1,7 +1,14 @@ import { ethers } from 'hardhat' import { Contract, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthVault, EthVaultMock, IKeeperRewards, Keeper, SharedMevEscrow } from '../typechain-types' +import { + EthVault, + EthVaultMock, + IKeeperRewards, + Keeper, + SharedMevEscrow, + DepositDataManager, +} from '../typechain-types' import { ThenArg } from '../helpers/types' import snapshotGasCost from './shared/snapshotGasCost' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' @@ -19,7 +26,11 @@ describe('EthVault - deposit', () => { const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' const referrer = '0x' + '1'.repeat(40) let sender: Wallet, receiver: Wallet, admin: Wallet, other: Wallet - let vault: EthVault, keeper: Keeper, mevEscrow: SharedMevEscrow, validatorsRegistry: Contract + let vault: EthVault, + keeper: Keeper, + mevEscrow: SharedMevEscrow, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] let createVaultMock: ThenArg>['createEthVaultMock'] @@ -32,6 +43,7 @@ describe('EthVault - deposit', () => { keeper, validatorsRegistry, sharedMevEscrow: mevEscrow, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault( admin, @@ -115,7 +127,7 @@ describe('EthVault - deposit', () => { }) it('fails when not harvested', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await updateRewards(keeper, [ { reward: ethers.parseEther('5'), @@ -141,7 +153,7 @@ describe('EthVault - deposit', () => { await vault .connect(other) .deposit(other.address, referrer, { value: ethers.parseEther('32') }) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault.connect(other).enterExitQueue(ethers.parseEther('32'), other.address) let vaultReward = ethers.parseEther('10') diff --git a/test/EthVault.liquidate.spec.ts b/test/EthVault.liquidate.spec.ts index 3cd7f67d..59bfb945 100644 --- a/test/EthVault.liquidate.spec.ts +++ b/test/EthVault.liquidate.spec.ts @@ -7,6 +7,7 @@ import { OsToken, OsTokenConfig, OsTokenVaultController, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import { ethVaultFixture } from './shared/fixtures' @@ -40,7 +41,8 @@ describe('EthVault - liquidate', () => { osTokenVaultController: OsTokenVaultController, osToken: OsToken, osTokenConfig: OsTokenConfig, - validatorsRegistry: Contract + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] @@ -53,13 +55,14 @@ describe('EthVault - liquidate', () => { osTokenVaultController, osToken, osTokenConfig, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault(admin, vaultParams) admin = await ethers.getImpersonatedSigner(await vault.admin()) await osTokenVaultController.connect(dao).setFeePercent(0) // collateralize vault - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const tx = await vault.connect(owner).deposit(owner.address, ZERO_ADDRESS, { value: assets }) shares = await extractDepositShares(tx) diff --git a/test/EthVault.mint.spec.ts b/test/EthVault.mint.spec.ts index b3069bf3..0548f90c 100644 --- a/test/EthVault.mint.spec.ts +++ b/test/EthVault.mint.spec.ts @@ -7,6 +7,7 @@ import { OsToken, VaultsRegistry, OsTokenVaultController, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import snapshotGasCost from './shared/snapshotGasCost' @@ -36,7 +37,8 @@ describe('EthVault - mint', () => { vaultsRegistry: VaultsRegistry, osTokenVaultController: OsTokenVaultController, osToken: OsToken, - validatorsRegistry: Contract + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] @@ -49,12 +51,13 @@ describe('EthVault - mint', () => { osToken, osTokenVaultController, vaultsRegistry, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault(admin, vaultParams) admin = await ethers.getImpersonatedSigner(await vault.admin()) // collateralize vault - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const tx = await vault.connect(sender).deposit(sender.address, ZERO_ADDRESS, { value: assets }) shares = await extractDepositShares(tx) osTokenShares = await osTokenVaultController.convertToShares(assets / 2n) diff --git a/test/EthVault.multicall.spec.ts b/test/EthVault.multicall.spec.ts index b3853acc..20363457 100644 --- a/test/EthVault.multicall.spec.ts +++ b/test/EthVault.multicall.spec.ts @@ -7,6 +7,7 @@ import { Keeper, MulticallMock, OwnMevEscrow__factory, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import snapshotGasCost from './shared/snapshotGasCost' @@ -35,7 +36,10 @@ describe('EthVault - multicall', () => { const metadataIpfsHash = '/ipfs/QmanU2bk9VsJuxhBmvfgXaC44fXpcC8DNHNxPZKMpNXo37' let sender: Wallet, admin: Signer - let vault: EthVault, keeper: Keeper, validatorsRegistry: Contract + let vault: EthVault, + keeper: Keeper, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] @@ -45,6 +49,7 @@ describe('EthVault - multicall', () => { createEthVault: createVault, keeper, validatorsRegistry, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault( admin, @@ -66,7 +71,7 @@ describe('EthVault - multicall', () => { await vault .connect(sender) .deposit(sender.address, referrer, { value: ethers.parseEther('32') }) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, depositDataManager, admin, validatorsRegistry) await setBalance(await mevEscrow.getAddress(), ethers.parseEther('10')) const userShares = await vault.getShares(sender.address) @@ -164,7 +169,7 @@ describe('EthVault - multicall', () => { it('fails to deposit, enter exit queue, update state and claim in one transaction', async () => { const vaultAddr = await vault.getAddress() - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) expect(await vault.isStateUpdateRequired()).to.eq(false) expect(await keeper.canHarvest(vaultAddr)).to.eq(false) diff --git a/test/EthVault.redeem.spec.ts b/test/EthVault.redeem.spec.ts index 274fe4ee..2c86660f 100644 --- a/test/EthVault.redeem.spec.ts +++ b/test/EthVault.redeem.spec.ts @@ -8,6 +8,7 @@ import { OsToken, OsTokenConfig, OsTokenVaultController, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import { ethVaultFixture } from './shared/fixtures' @@ -51,7 +52,8 @@ describe('EthVault - redeem osToken', () => { osTokenVaultController: OsTokenVaultController, osToken: OsToken, osTokenConfig: OsTokenConfig, - validatorsRegistry: Contract + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] @@ -64,13 +66,14 @@ describe('EthVault - redeem osToken', () => { osTokenVaultController, osToken, osTokenConfig, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault(admin, vaultParams) admin = await ethers.getImpersonatedSigner(await vault.admin()) await osTokenVaultController.connect(dao).setFeePercent(0) // collateralize vault - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const tx = await vault.connect(owner).deposit(owner.address, ZERO_ADDRESS, { value: assets }) shares = await extractDepositShares(tx) diff --git a/test/EthVault.register.spec.ts b/test/EthVault.register.spec.ts index 5ec5684b..a64e6a1b 100644 --- a/test/EthVault.register.spec.ts +++ b/test/EthVault.register.spec.ts @@ -13,19 +13,16 @@ import { EthValidatorsData, exitSignatureIpfsHashes, getEthValidatorsSigningData, - getValidatorProof, - getValidatorsMultiProof, getWithdrawalCredentials, - ValidatorsMultiProof, } from './shared/validators' import { ethVaultFixture, getOraclesSignatures } from './shared/fixtures' import { MAX_UINT256, - PANIC_CODES, VALIDATORS_DEADLINE, VALIDATORS_MIN_ORACLES, ZERO_ADDRESS, } from './shared/constants' +import { getHarvestParams, updateRewards } from './shared/rewards' const gwei = 1000000000n const uintSerializer = new UintNumberType(8) @@ -37,7 +34,7 @@ describe('EthVault - register', () => { const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' const deadline = VALIDATORS_DEADLINE - let admin: Signer, other: Wallet + let admin: Signer, other: Wallet, keysManager: Wallet let vault: EthVault, keeper: Keeper, validatorsRegistry: Contract let validatorsData: EthValidatorsData let validatorsRegistryRoot: string @@ -45,7 +42,7 @@ describe('EthVault - register', () => { let createVault: ThenArg>['createEthVault'] before('create fixture loader', async () => { - ;[admin, other] = (await (ethers as any).getSigners()).slice(1, 3) + ;[admin, other, keysManager] = (await (ethers as any).getSigners()).slice(1, 4) }) beforeEach('deploy fixture', async () => { @@ -64,17 +61,15 @@ describe('EthVault - register', () => { validatorsData = await createEthValidatorsData(vault) validatorsRegistryRoot = await validatorsRegistry.get_deposit_root() await vault.connect(other).deposit(other.address, ZERO_ADDRESS, { value: validatorDeposit }) - await vault.connect(admin).setValidatorsRoot(validatorsData.root) + await vault.connect(admin).setKeysManager(keysManager.address) }) describe('single validator', () => { let validator: Buffer - let proof: string[] let approvalParams: IKeeperValidators.ApprovalParamsStruct beforeEach(async () => { validator = validatorsData.validators[0] - proof = getValidatorProof(validatorsData.tree, validator, 0) const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] const signatures = getOraclesSignatures( await getEthValidatorsSigningData( @@ -96,55 +91,74 @@ describe('EthVault - register', () => { } }) + it('fails from non-keys manager', async () => { + await expect( + vault.connect(other).registerValidators(approvalParams) + ).to.be.revertedWithCustomError(vault, 'AccessDenied') + }) + it('fails with not enough withdrawable assets', async () => { await setBalance(await vault.getAddress(), ethers.parseEther('31.9')) - await expect(vault.registerValidator(approvalParams, proof)).to.be.revertedWithCustomError( - vault, - 'InsufficientAssets' - ) + await expect( + vault.connect(keysManager).registerValidators(approvalParams) + ).to.be.revertedWithCustomError(vault, 'InsufficientAssets') }) - it('fails with invalid proof', async () => { - const invalidProof = getValidatorProof(validatorsData.tree, validatorsData.validators[1], 1) + it('fails when not harvested', async () => { + // collateralize + const vaultReward = getHarvestParams(await vault.getAddress(), 1n, 0n) + const tree = await updateRewards(keeper, [vaultReward]) + const proof = tree.getProof([ + vaultReward.vault, + vaultReward.reward, + vaultReward.unlockedMevReward, + ]) + await vault.updateState({ + rewardsRoot: tree.root, + reward: vaultReward.reward, + unlockedMevReward: vaultReward.unlockedMevReward, + proof, + }) + + // make vault not harvested + await updateRewards(keeper, [vaultReward]) + await updateRewards(keeper, [vaultReward]) await expect( - vault.registerValidator(approvalParams, invalidProof) - ).to.be.revertedWithCustomError(vault, 'InvalidProof') + vault.connect(keysManager).registerValidators(approvalParams) + ).to.be.revertedWithCustomError(vault, 'NotHarvested') }) it('fails with invalid validator length', async () => { const invalidValidator = appendDepositData( - validator, + Buffer.alloc(1), validatorDeposit, await vault.getAddress() ) const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] await expect( - vault.registerValidator( - { - validatorsRegistryRoot, - validators: appendDepositData(validator, validatorDeposit, await vault.getAddress()), - deadline, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - invalidValidator, - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + validators: invalidValidator, + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + invalidValidator, + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - proof - ) - ).to.be.revertedWithCustomError(vault, 'InvalidValidator') + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) + ).to.be.revertedWithCustomError(vault, 'InvalidValidators') }) it('succeeds', async () => { const index = await validatorsRegistry.get_deposit_count() - const receipt = await vault.registerValidator(approvalParams, proof) + const receipt = await vault.connect(keysManager).registerValidators(approvalParams) const publicKey = `0x${validator.subarray(0, 48).toString('hex')}` await expect(receipt).to.emit(vault, 'ValidatorRegistered').withArgs(publicKey) await expect(receipt) @@ -162,20 +176,13 @@ describe('EthVault - register', () => { describe('multiple validators', () => { let validators: Buffer[] - let indexes: number[] let approvalParams: IKeeperValidators.ApprovalParamsStruct - let multiProof: ValidatorsMultiProof let signatures: Buffer beforeEach(async () => { - multiProof = getValidatorsMultiProof(validatorsData.tree, validatorsData.validators, [ - ...Array(validatorsData.validators.length).keys(), - ]) validators = validatorsData.validators const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] - const sortedVals = multiProof.leaves.map((v) => v[0]) const vaultAddr = await vault.getAddress() - indexes = validators.map((v) => sortedVals.indexOf(v)) const balance = validatorDeposit * BigInt(validators.length) + (await vault.totalExitingAssets()) + @@ -204,35 +211,30 @@ describe('EthVault - register', () => { it('fails with not enough withdrawable assets', async () => { await setBalance(await vault.getAddress(), validatorDeposit * BigInt(validators.length - 1)) await expect( - vault.registerValidators(approvalParams, indexes, multiProof.proofFlags, multiProof.proof) + vault.connect(keysManager).registerValidators(approvalParams) ).to.be.revertedWithCustomError(vault, 'InsufficientAssets') }) it('fails with invalid validators count', async () => { const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] await expect( - vault.registerValidators( - { - validatorsRegistryRoot, - validators: Buffer.from(''), - deadline, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - Buffer.from(''), - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + validators: Buffer.from(''), + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + Buffer.from(''), + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) ).to.be.revertedWithCustomError(vault, 'InvalidValidators') }) @@ -249,28 +251,23 @@ describe('EthVault - register', () => { const invalidValidatorsConcat = Buffer.concat(invalidValidators) const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] await expect( - vault.registerValidators( - { - validatorsRegistryRoot, - deadline, - validators: invalidValidatorsConcat, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - invalidValidatorsConcat, - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + deadline, + validators: invalidValidatorsConcat, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + invalidValidatorsConcat, + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) ).to.be.revertedWith( 'DepositContract: reconstructed DepositData does not match supplied deposit_data_root' ) @@ -288,28 +285,23 @@ describe('EthVault - register', () => { const invalidValidatorsConcat = Buffer.concat(invalidValidators) const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] await expect( - vault.registerValidators( - { - validatorsRegistryRoot, - validators: invalidValidatorsConcat, - deadline, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - invalidValidatorsConcat, - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + validators: invalidValidatorsConcat, + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + invalidValidatorsConcat, + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) ).to.be.revertedWith( 'DepositContract: reconstructed DepositData does not match supplied deposit_data_root' ) @@ -327,83 +319,28 @@ describe('EthVault - register', () => { const invalidValidatorsConcat = Buffer.concat(invalidValidators) const exitSignaturesIpfsHash = exitSignatureIpfsHashes[0] await expect( - vault.registerValidators( - { - validatorsRegistryRoot, - validators: invalidValidatorsConcat, - deadline, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - invalidValidatorsConcat, - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + validators: invalidValidatorsConcat, + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + invalidValidatorsConcat, + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) ).to.be.revertedWith( 'DepositContract: reconstructed DepositData does not match supplied deposit_data_root' ) }) - it('fails with invalid proof', async () => { - const invalidMultiProof = getValidatorsMultiProof( - validatorsData.tree, - validators.slice(1), - [...Array(validatorsData.validators.length).keys()].slice(1) - ) - - await expect( - vault.registerValidators( - approvalParams, - indexes, - invalidMultiProof.proofFlags, - invalidMultiProof.proof - ) - ).to.be.revertedWithCustomError(vault, 'MerkleProofInvalidMultiproof') - }) - - it('fails with invalid indexes', async () => { - await expect( - vault.registerValidators(approvalParams, [], multiProof.proofFlags, multiProof.proof) - ).to.be.revertedWithCustomError(vault, 'InvalidValidators') - - await expect( - vault.registerValidators( - approvalParams, - indexes.map((i) => i + 1), - multiProof.proofFlags, - multiProof.proof - ) - ).to.be.revertedWithPanic(PANIC_CODES.OUT_OF_BOUND_INDEX) - - await expect( - vault.registerValidators( - approvalParams, - indexes.slice(1), - multiProof.proofFlags, - multiProof.proof - ) - ).to.be.revertedWithCustomError(vault, 'InvalidValidators') - - await expect( - vault.registerValidators( - approvalParams, - indexes.sort(() => 0.5 - Math.random()), - multiProof.proofFlags, - multiProof.proof - ) - ).to.be.revertedWithCustomError(vault, 'InvalidProof') - }) - it('fails with invalid validator length', async () => { const invalidValidators = [ validators[0].subarray(0, 100), @@ -413,28 +350,23 @@ describe('EthVault - register', () => { for (let i = 0; i < invalidValidators.length; i++) { await expect( - vault.registerValidators( - { - validatorsRegistryRoot, - validators: invalidValidators[i], - deadline, - signatures: getOraclesSignatures( - await getEthValidatorsSigningData( - invalidValidators[i], - deadline, - exitSignaturesIpfsHash, - keeper, - vault, - validatorsRegistryRoot - ), - VALIDATORS_MIN_ORACLES + vault.connect(keysManager).registerValidators({ + validatorsRegistryRoot, + validators: invalidValidators[i], + deadline, + signatures: getOraclesSignatures( + await getEthValidatorsSigningData( + invalidValidators[i], + deadline, + exitSignaturesIpfsHash, + keeper, + vault, + validatorsRegistryRoot ), - exitSignaturesIpfsHash, - }, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + VALIDATORS_MIN_ORACLES + ), + exitSignaturesIpfsHash, + }) ).to.be.revertedWithCustomError(vault, 'InvalidValidators') } }) @@ -443,12 +375,7 @@ describe('EthVault - register', () => { const startIndex = uintSerializer.deserialize( ethers.getBytes(await validatorsRegistry.get_deposit_count()) ) - const receipt = await vault.registerValidators( - approvalParams, - indexes, - multiProof.proofFlags, - multiProof.proof - ) + const receipt = await vault.connect(keysManager).registerValidators(approvalParams) for (let i = 0; i < validators.length; i++) { const validator = validators[i] const publicKey = toHexString(validator.subarray(0, 48)) diff --git a/test/EthVault.settings.spec.ts b/test/EthVault.settings.spec.ts index 5dbd3a44..0b99e3a1 100644 --- a/test/EthVault.settings.spec.ts +++ b/test/EthVault.settings.spec.ts @@ -1,7 +1,7 @@ import { ethers } from 'hardhat' import { Contract, Wallet } from 'ethers' -import { EthVault, Keeper } from '../typechain-types' +import { EthVault, Keeper, DepositDataManager } from '../typechain-types' import { ThenArg } from '../helpers/types' import { ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -17,7 +17,7 @@ describe('EthVault - settings', () => { let createVault: ThenArg>['createEthVault'] let admin: Wallet, keysManager: Wallet, other: Wallet, newFeeRecipient: Wallet - let keeper: Keeper, validatorsRegistry: Contract + let keeper: Keeper, validatorsRegistry: Contract, depositDataManager: DepositDataManager before('create fixture loader', async () => { ;[admin, keysManager, other, newFeeRecipient] = (await (ethers as any).getSigners()).slice(1, 5) @@ -27,6 +27,7 @@ describe('EthVault - settings', () => { ;({ keeper, validatorsRegistry, + depositDataManager, createEthVault: createVault, } = await loadFixture(ethVaultFixture)) }) @@ -58,39 +59,6 @@ describe('EthVault - settings', () => { }) }) - describe('validators root', () => { - const newValidatorsRoot = '0x059a8487a1ce461e9670c4646ef85164ae8791613866d28c972fb351dc45c606' - let vault: EthVault - - beforeEach('deploy vault', async () => { - vault = await createVault( - admin, - { - capacity, - feePercent, - metadataIpfsHash, - }, - false, - true - ) - await vault.connect(admin).setKeysManager(keysManager.address) - }) - - it('only keys manager can update', async () => { - await expect( - vault.connect(admin).setValidatorsRoot(newValidatorsRoot) - ).to.be.revertedWithCustomError(vault, 'AccessDenied') - }) - - it('can update', async () => { - const receipt = await vault.connect(keysManager).setValidatorsRoot(newValidatorsRoot) - await expect(receipt) - .to.emit(vault, 'ValidatorsRootUpdated') - .withArgs(keysManager.address, newValidatorsRoot) - await snapshotGasCost(receipt) - }) - }) - describe('keys manager', () => { let vault: EthVault @@ -113,16 +81,9 @@ describe('EthVault - settings', () => { ).to.be.revertedWithCustomError(vault, 'AccessDenied') }) - it('cannot set to zero address', async () => { - await expect(vault.connect(admin).setKeysManager(ZERO_ADDRESS)).to.be.revertedWithCustomError( - vault, - 'ZeroAddress' - ) - }) - it('can be updated by admin', async () => { // initially equals to admin - expect(await vault.keysManager()).to.be.eq(admin.address) + expect(await vault.keysManager()).to.be.eq(await depositDataManager.getAddress()) const receipt = await vault.connect(admin).setKeysManager(keysManager.address) await expect(receipt) .to.emit(vault, 'KeysManagerUpdated') @@ -146,7 +107,7 @@ describe('EthVault - settings', () => { false, true ) - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) }) it('only admin can update', async () => { diff --git a/test/EthVault.state.spec.ts b/test/EthVault.state.spec.ts index a71881cc..fa059bc0 100644 --- a/test/EthVault.state.spec.ts +++ b/test/EthVault.state.spec.ts @@ -9,6 +9,7 @@ import { VaultsRegistry, OsTokenVaultController, OsTokenConfig, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import snapshotGasCost from './shared/snapshotGasCost' @@ -44,7 +45,8 @@ describe('EthVault - state', () => { validatorsRegistry: Contract, vaultsRegistry: VaultsRegistry, osTokenVaultController: OsTokenVaultController, - osTokenConfig: OsTokenConfig + osTokenConfig: OsTokenConfig, + depositDataManager: DepositDataManager let createVault: ThenArg>['createEthVault'] let createVaultMock: ThenArg>['createEthVaultMock'] @@ -60,6 +62,7 @@ describe('EthVault - state', () => { vaultsRegistry, osTokenVaultController, osTokenConfig, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createVault(admin, { capacity, @@ -138,7 +141,7 @@ describe('EthVault - state', () => { ) const securityDeposit = 1000000000n await vault.connect(other).deposit(other.address, ZERO_ADDRESS, { value: 1 }) - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await vault._setTotalAssets(1) expect(await vault.totalAssets()).to.eq(1) expect(await vault.totalShares()).to.eq(securityDeposit + 1n) diff --git a/test/EthVault.upgrade.spec.ts b/test/EthVault.upgrade.spec.ts index 0a8fbfe1..a6092e06 100644 --- a/test/EthVault.upgrade.spec.ts +++ b/test/EthVault.upgrade.spec.ts @@ -11,6 +11,7 @@ import { OsTokenVaultController, SharedMevEscrow, VaultsRegistry, + DepositDataManager, } from '../typechain-types' import snapshotGasCost from './shared/snapshotGasCost' import { @@ -50,6 +51,7 @@ describe('EthVault - upgrade', () => { sharedMevEscrow: SharedMevEscrow, osTokenConfig: OsTokenConfig, osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager, ethVaultFactory: EthVaultFactory, ethPrivVaultFactory: EthVaultFactory, ethErc20VaultFactory: EthVaultFactory, @@ -70,6 +72,7 @@ describe('EthVault - upgrade', () => { sharedMevEscrow = fixture.sharedMevEscrow osTokenConfig = fixture.osTokenConfig osTokenVaultController = fixture.osTokenVaultController + depositDataManager = fixture.depositDataManager ethVaultFactory = fixture.ethVaultFactory ethPrivVaultFactory = fixture.ethPrivVaultFactory ethErc20VaultFactory = fixture.ethErc20VaultFactory @@ -90,6 +93,7 @@ describe('EthVault - upgrade', () => { fixture.osTokenVaultController, fixture.osTokenConfig, fixture.sharedMevEscrow, + fixture.depositDataManager, EXITING_ASSETS_MIN_DELAY ) currImpl = await vault.implementation() @@ -139,6 +143,7 @@ describe('EthVault - upgrade', () => { fixture.osTokenVaultController, fixture.osTokenConfig, fixture.sharedMevEscrow, + fixture.depositDataManager, EXITING_ASSETS_MIN_DELAY ) callData = ethers.AbiCoder.defaultAbiCoder().encode(['uint128'], [100]) @@ -158,6 +163,7 @@ describe('EthVault - upgrade', () => { fixture.osTokenVaultController, fixture.osTokenConfig, fixture.sharedMevEscrow, + fixture.depositDataManager, EXITING_ASSETS_MIN_DELAY ) callData = ethers.AbiCoder.defaultAbiCoder().encode(['uint128'], [100]) @@ -265,12 +271,12 @@ describe('EthVault - upgrade', () => { const userShares = await vault.getShares(other.address) const userAssets = await vault.convertToAssets(userShares) const osTokenPosition = await vault.osTokenPositions(other.address) - const keysManager = await vault.keysManager() const mevEscrow = await vault.mevEscrow() const totalAssets = await vault.totalAssets() const totalShares = await vault.totalShares() const validatorIndex = await vault.validatorIndex() const validatorsRoot = await vault.validatorsRoot() + const vaultAddress = await vault.getAddress() expect(await vault.version()).to.be.eq(1) const receipt = await vault.connect(admin).upgradeToAndCall(newImpl, '0x') @@ -279,12 +285,12 @@ describe('EthVault - upgrade', () => { expect(await vault.getShares(other.address)).to.be.eq(userShares) expect(await vault.convertToAssets(userShares)).to.be.deep.eq(userAssets) expect(await vault.osTokenPositions(other.address)).to.be.above(osTokenPosition) - expect(await vault.keysManager()).to.be.eq(keysManager) + expect(await vault.keysManager()).to.be.eq(await depositDataManager.getAddress()) expect(await vault.mevEscrow()).to.be.eq(mevEscrow) expect(await vault.totalAssets()).to.be.eq(totalAssets) expect(await vault.totalShares()).to.be.eq(totalShares) - expect(await vault.validatorIndex()).to.be.eq(validatorIndex) - expect(await vault.validatorsRoot()).to.be.eq(validatorsRoot) + expect(await depositDataManager.depositDataIndexes(vaultAddress)).to.be.eq(validatorIndex) + expect(await depositDataManager.depositDataRoots(vaultAddress)).to.be.eq(validatorsRoot) await snapshotGasCost(receipt) } await checkVault(vaults[0], await ethVaultFactory.implementation()) diff --git a/test/EthVault.whitelist.spec.ts b/test/EthVault.whitelist.spec.ts index 9d7ac2be..98842a3a 100644 --- a/test/EthVault.whitelist.spec.ts +++ b/test/EthVault.whitelist.spec.ts @@ -1,7 +1,7 @@ import { ethers } from 'hardhat' import { Contract, Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthPrivVault, Keeper, IKeeperRewards } from '../typechain-types' +import { EthPrivVault, Keeper } from '../typechain-types' import { ThenArg } from '../helpers/types' import { createDepositorMock, ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -22,7 +22,10 @@ describe('EthVault - whitelist', () => { const referrer = ZERO_ADDRESS const metadataIpfsHash = 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u' let sender: Wallet, whitelister: Wallet, admin: Signer, other: Wallet - let vault: EthPrivVault, keeper: Keeper, validatorsRegistry: Contract + let vault: EthPrivVault, + keeper: Keeper, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let createPrivateVault: ThenArg>['createEthPrivVault'] @@ -35,6 +38,7 @@ describe('EthVault - whitelist', () => { createEthPrivVault: createPrivateVault, keeper, validatorsRegistry, + depositDataManager, } = await loadFixture(ethVaultFixture)) vault = await createPrivateVault(admin, { capacity, @@ -114,7 +118,7 @@ describe('EthVault - whitelist', () => { }) it('cannot update state and call', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const vaultReward = getHarvestParams(await vault.getAddress(), ethers.parseEther('1'), 0n) const tree = await updateRewards(keeper, [vaultReward]) diff --git a/test/KeeperRewards.spec.ts b/test/KeeperRewards.spec.ts index b2e5f0f6..1764d33f 100644 --- a/test/KeeperRewards.spec.ts +++ b/test/KeeperRewards.spec.ts @@ -7,6 +7,7 @@ import { Keeper, OsTokenVaultController, SharedMevEscrow, + DepositDataManager, } from '../typechain-types' import { ThenArg } from '../helpers/types' import { ethVaultFixture, getOraclesSignatures } from './shared/fixtures' @@ -40,7 +41,8 @@ describe('KeeperRewards', () => { let keeper: Keeper, validatorsRegistry: Contract, sharedMevEscrow: SharedMevEscrow, - osTokenVaultController: OsTokenVaultController + osTokenVaultController: OsTokenVaultController, + depositDataManager: DepositDataManager let globalRewardsNonce: number beforeEach(async () => { @@ -51,6 +53,7 @@ describe('KeeperRewards', () => { validatorsRegistry, sharedMevEscrow, osTokenVaultController, + depositDataManager, } = await loadFixture(ethVaultFixture)) await setBalance(oracle.address, ethers.parseEther('10000')) }) @@ -290,7 +293,7 @@ describe('KeeperRewards', () => { await vault .connect(admin) .deposit(await admin.getAddress(), ZERO_ADDRESS, { value: validatorDeposit }) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, depositDataManager, admin, validatorsRegistry) expect(await keeper.isCollateralized(await vault.getAddress())).to.equal(true) expect(await keeper.canHarvest(await vault.getAddress())).to.equal(false) @@ -416,13 +419,13 @@ describe('KeeperRewards', () => { it('fails for invalid reward', async () => { await expect( ownMevVault.updateState({ ...harvestParams, reward: 0 }) - ).to.be.revertedWithCustomError(ownMevVault, 'InvalidProof') + ).to.be.revertedWithCustomError(keeper, 'InvalidProof') }) it('fails for invalid proof', async () => { await expect( ownMevVault.updateState({ ...harvestParams, proof: [] }) - ).to.be.revertedWithCustomError(ownMevVault, 'InvalidProof') + ).to.be.revertedWithCustomError(keeper, 'InvalidProof') }) it('fails for invalid root', async () => { @@ -664,13 +667,13 @@ describe('KeeperRewards', () => { it('fails for invalid unlocked MEV reward', async () => { await expect( sharedMevVault.updateState({ ...harvestParams, unlockedMevReward: 0n }) - ).to.be.revertedWithCustomError(sharedMevVault, 'InvalidProof') + ).to.be.revertedWithCustomError(keeper, 'InvalidProof') }) it('fails for invalid proof', async () => { await expect( sharedMevVault.updateState({ ...harvestParams, proof: [] }) - ).to.be.revertedWithCustomError(sharedMevVault, 'InvalidProof') + ).to.be.revertedWithCustomError(keeper, 'InvalidProof') }) it('fails for invalid root', async () => { diff --git a/test/KeeperValidators.spec.ts b/test/KeeperValidators.spec.ts index 883daafc..76ffd8e8 100644 --- a/test/KeeperValidators.spec.ts +++ b/test/KeeperValidators.spec.ts @@ -1,7 +1,7 @@ import { ethers } from 'hardhat' import { Contract, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthVault, IKeeperValidators, Keeper } from '../typechain-types' +import { EthVault, IKeeperValidators, Keeper, DepositDataManager } from '../typechain-types' import { ThenArg } from '../helpers/types' import { ethVaultFixture, getOraclesSignatures } from './shared/fixtures' import { expect } from './shared/expect' @@ -10,6 +10,7 @@ import { VALIDATORS_DEADLINE, VALIDATORS_MIN_ORACLES, ZERO_ADDRESS, + ZERO_BYTES32, } from './shared/constants' import { createEthValidatorsData, @@ -37,7 +38,10 @@ describe('KeeperValidators', () => { let createVault: ThenArg>['createEthVault'] let sender: Wallet, dao: Wallet, admin: Wallet - let keeper: Keeper, vault: EthVault, validatorsRegistry: Contract + let keeper: Keeper, + vault: EthVault, + validatorsRegistry: Contract, + depositDataManager: DepositDataManager let validatorsData: EthValidatorsData let validatorsRegistryRoot: string @@ -46,6 +50,7 @@ describe('KeeperValidators', () => { ;({ keeper, validatorsRegistry, + depositDataManager, createEthVault: createVault, } = await loadFixture(ethVaultFixture)) vault = await createVault( @@ -60,7 +65,9 @@ describe('KeeperValidators', () => { ) validatorsData = await createEthValidatorsData(vault) validatorsRegistryRoot = await validatorsRegistry.get_deposit_root() - await vault.connect(admin).setValidatorsRoot(validatorsData.root) + await depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), validatorsData.root) }) describe('register single validator', () => { @@ -106,15 +113,15 @@ describe('KeeperValidators', () => { validator.subarray(144, 176), { value: depositAmount } ) - await expect(vault.registerValidator(approveParams, proof)).revertedWithCustomError( - keeper, - 'InvalidValidatorsRegistryRoot' - ) + await expect( + depositDataManager.registerValidator(await vault.getAddress(), approveParams, proof) + ).revertedWithCustomError(keeper, 'InvalidValidatorsRegistryRoot') }) it('fails for invalid signatures', async () => { await expect( - vault.registerValidator( + depositDataManager.registerValidator( + await vault.getAddress(), { ...approveParams, signatures: getOraclesSignatures(signingData, VALIDATORS_MIN_ORACLES - 1), @@ -126,7 +133,8 @@ describe('KeeperValidators', () => { it('fails for invalid deadline', async () => { await expect( - vault.registerValidator( + depositDataManager.registerValidator( + await vault.getAddress(), { ...approveParams, deadline: deadline + 1n, @@ -138,7 +146,8 @@ describe('KeeperValidators', () => { it('fails for expired deadline', async () => { await expect( - vault.registerValidator( + depositDataManager.registerValidator( + await vault.getAddress(), { ...approveParams, deadline: await getLatestBlockTimestamp(), @@ -150,7 +159,8 @@ describe('KeeperValidators', () => { it('fails for invalid validator', async () => { await expect( - vault.registerValidator( + depositDataManager.registerValidator( + await vault.getAddress(), { ...approveParams, validators: validatorsData.validators[1], @@ -162,7 +172,8 @@ describe('KeeperValidators', () => { it('fails for invalid proof', async () => { await expect( - vault.registerValidator( + depositDataManager.registerValidator( + await vault.getAddress(), approveParams, getValidatorProof(validatorsData.tree, validatorsData.validators[1], 1) ) @@ -175,7 +186,11 @@ describe('KeeperValidators', () => { expect(rewards.assets).to.eq(0) const globalRewardsNonce = await keeper.rewardsNonce() - let receipt = await vault.registerValidator(approveParams, proof) + let receipt = await depositDataManager.registerValidator( + await vault.getAddress(), + approveParams, + proof + ) await expect(receipt) .to.emit(keeper, 'ValidatorsApproval') .withArgs(await vault.getAddress(), approveParams.exitSignaturesIpfsHash) @@ -190,10 +205,9 @@ describe('KeeperValidators', () => { const newValidatorsRegistryRoot = await validatorsRegistry.get_deposit_root() // fails to register twice - await expect(vault.registerValidator(approveParams, proof)).revertedWithCustomError( - keeper, - 'InvalidValidatorsRegistryRoot' - ) + await expect( + depositDataManager.registerValidator(await vault.getAddress(), approveParams, proof) + ).revertedWithCustomError(keeper, 'InvalidValidatorsRegistryRoot') const newValidator = validatorsData.validators[1] const newExitSignatureIpfsHash = exitSignatureIpfsHashes[1] @@ -209,7 +223,8 @@ describe('KeeperValidators', () => { newValidatorsRegistryRoot ) const newSignatures = getOraclesSignatures(newSigningData, ORACLES.length) - receipt = await vault.registerValidator( + receipt = await depositDataManager.registerValidator( + await vault.getAddress(), { validatorsRegistryRoot: newValidatorsRegistryRoot, validators: newValidator, @@ -282,13 +297,20 @@ describe('KeeperValidators', () => { { value: depositAmount } ) await expect( - vault.registerValidators(approveParams, indexes, proof.proofFlags, proof.proof) + depositDataManager.registerValidators( + await vault.getAddress(), + approveParams, + indexes, + proof.proofFlags, + proof.proof + ) ).revertedWithCustomError(keeper, 'InvalidValidatorsRegistryRoot') }) it('fails for invalid signatures', async () => { await expect( - vault.registerValidators( + depositDataManager.registerValidators( + await vault.getAddress(), { ...approveParams, signatures: getOraclesSignatures(signingData, VALIDATORS_MIN_ORACLES - 1), @@ -302,7 +324,8 @@ describe('KeeperValidators', () => { it('fails for invalid validators', async () => { await expect( - vault.registerValidators( + depositDataManager.registerValidators( + await vault.getAddress(), { ...approveParams, validators: validators[0], @@ -316,7 +339,8 @@ describe('KeeperValidators', () => { it('fails for invalid deadline', async () => { await expect( - vault.registerValidators( + depositDataManager.registerValidators( + await vault.getAddress(), { ...approveParams, deadline: deadline + 1n, @@ -330,7 +354,8 @@ describe('KeeperValidators', () => { it('fails for expired deadline', async () => { await expect( - vault.registerValidators( + depositDataManager.registerValidators( + await vault.getAddress(), { ...approveParams, deadline: await getLatestBlockTimestamp(), @@ -346,7 +371,8 @@ describe('KeeperValidators', () => { const invalidProof = getValidatorsMultiProof(validatorsData.tree, [validators[0]], [0]) const exitSignaturesIpfsHash = approveParams.exitSignaturesIpfsHash as string await expect( - vault.registerValidators( + depositDataManager.registerValidators( + await vault.getAddress(), { validatorsRegistryRoot, deadline, @@ -378,7 +404,8 @@ describe('KeeperValidators', () => { const validatorsConcat = Buffer.concat(validators) const globalRewardsNonce = await keeper.rewardsNonce() - let receipt = await vault.registerValidators( + let receipt = await depositDataManager.registerValidators( + await vault.getAddress(), approveParams, indexes, proof.proofFlags, @@ -398,7 +425,13 @@ describe('KeeperValidators', () => { // fails to register twice await expect( - vault.registerValidators(approveParams, indexes, proof.proofFlags, proof.proof) + depositDataManager.registerValidators( + await vault.getAddress(), + approveParams, + indexes, + proof.proofFlags, + proof.proof + ) ).revertedWithCustomError(keeper, 'InvalidValidatorsRegistryRoot') await vault @@ -406,7 +439,12 @@ describe('KeeperValidators', () => { .deposit(sender.address, referrer, { value: depositAmount * BigInt(validators.length) }) // reset validator index - await vault.connect(admin).setValidatorsRoot(validatorsData.root) + await depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), ZERO_BYTES32) + await depositDataManager + .connect(admin) + .setDepositDataRoot(await vault.getAddress(), validatorsData.root) const newSigningData = await getEthValidatorsSigningData( validatorsConcat, deadline, @@ -416,7 +454,8 @@ describe('KeeperValidators', () => { newValidatorsRegistryRoot ) const newSignatures = getOraclesSignatures(newSigningData, ORACLES.length) - receipt = await vault.registerValidators( + receipt = await depositDataManager.registerValidators( + await vault.getAddress(), { validatorsRegistryRoot: newValidatorsRegistryRoot, deadline, @@ -480,7 +519,7 @@ describe('KeeperValidators', () => { }) it('fails for invalid signatures', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await expect( keeper.updateExitSignatures( await vault.getAddress(), @@ -492,7 +531,7 @@ describe('KeeperValidators', () => { }) it('fails for invalid deadline', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await expect( keeper.updateExitSignatures( await vault.getAddress(), @@ -504,7 +543,7 @@ describe('KeeperValidators', () => { }) it('fails for expired deadline', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const newDeadline = await getLatestBlockTimestamp() const newSigningData = await getEthValidatorsExitSignaturesSigningData( keeper, @@ -524,7 +563,7 @@ describe('KeeperValidators', () => { }) it('fails to submit update twice', async () => { - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) await keeper.updateExitSignatures( await vault.getAddress(), deadline, @@ -546,7 +585,7 @@ describe('KeeperValidators', () => { const nonce = await keeper.exitSignaturesNonces(await vault.getAddress()) expect(nonce).to.eq(0) - await collateralizeEthVault(vault, keeper, validatorsRegistry, admin) + await collateralizeEthVault(vault, keeper, depositDataManager, admin, validatorsRegistry) const receipt = await keeper .connect(sender) diff --git a/test/OsToken.spec.ts b/test/OsToken.spec.ts index 7733a312..c568243f 100644 --- a/test/OsToken.spec.ts +++ b/test/OsToken.spec.ts @@ -1,7 +1,13 @@ import { ethers, network } from 'hardhat' import { Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { OsTokenVaultController, OsToken, EthVault, Keeper } from '../typechain-types' +import { + OsTokenVaultController, + OsToken, + EthVault, + Keeper, + DepositDataManager, +} from '../typechain-types' import snapshotGasCost from './shared/snapshotGasCost' import { ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -31,7 +37,8 @@ describe('OsToken', () => { let osTokenVaultController: OsTokenVaultController, osToken: OsToken, vault: EthVault, - keeper: Keeper + keeper: Keeper, + depositDataManager: DepositDataManager before('create fixture loader', async () => { ;[dao, initialHolder, admin, spender, recipient] = await (ethers as any).getSigners() @@ -47,11 +54,19 @@ describe('OsToken', () => { osTokenVaultController = fixture.osTokenVaultController keeper = fixture.keeper osToken = fixture.osToken + depositDataManager = fixture.depositDataManager + vault = await fixture.createEthVault(admin, vaultParams) admin = await ethers.getImpersonatedSigner(await vault.admin()) // collateralize vault - await collateralizeEthVault(vault, fixture.keeper, fixture.validatorsRegistry, admin) + await collateralizeEthVault( + vault, + fixture.keeper, + depositDataManager, + admin, + fixture.validatorsRegistry + ) await vault .connect(initialHolder) .deposit(initialHolder.address, ZERO_ADDRESS, { value: assets }) diff --git a/test/PriceFeed.test.ts b/test/PriceFeed.test.ts index aed76aee..bda6a6fb 100644 --- a/test/PriceFeed.test.ts +++ b/test/PriceFeed.test.ts @@ -1,7 +1,13 @@ import { ethers } from 'hardhat' import { Signer, Wallet } from 'ethers' import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers' -import { EthVault, IKeeperRewards, OsTokenVaultController, PriceFeed } from '../typechain-types' +import { + EthVault, + IKeeperRewards, + OsTokenVaultController, + PriceFeed, + DepositDataManager, +} from '../typechain-types' import { expect } from './shared/expect' import { createPriceFeed, ethVaultFixture } from './shared/fixtures' import { ONE_DAY, ZERO_ADDRESS } from './shared/constants' @@ -25,7 +31,10 @@ describe('PriceFeed', () => { metadataIpfsHash: 'bafkreidivzimqfqtoqxkrpge6bjyhlvxqs3rhe73owtmdulaxr5do5in7u', } let sender: Wallet, admin: Signer, dao: Wallet - let osTokenVaultController: OsTokenVaultController, priceFeed: PriceFeed, vault: EthVault + let osTokenVaultController: OsTokenVaultController, + priceFeed: PriceFeed, + vault: EthVault, + depositDataManager: DepositDataManager before('create fixture loader', async () => { ;[sender, dao, admin] = await (ethers as any).getSigners() @@ -37,10 +46,17 @@ describe('PriceFeed', () => { admin = await ethers.getImpersonatedSigner(await vault.admin()) osTokenVaultController = fixture.osTokenVaultController + depositDataManager = fixture.depositDataManager priceFeed = await createPriceFeed(osTokenVaultController, description) // collateralize vault - await collateralizeEthVault(vault, fixture.keeper, fixture.validatorsRegistry, admin) + await collateralizeEthVault( + vault, + fixture.keeper, + depositDataManager, + admin, + fixture.validatorsRegistry + ) await vault.connect(sender).deposit(sender.address, ZERO_ADDRESS, { value: shares }) const reward = ethers.parseEther('1') diff --git a/test/RewardSplitter.spec.ts b/test/RewardSplitter.spec.ts index 91e508c6..44b72ca2 100644 --- a/test/RewardSplitter.spec.ts +++ b/test/RewardSplitter.spec.ts @@ -7,6 +7,7 @@ import { Keeper, RewardSplitter, RewardSplitter__factory, + DepositDataManager, } from '../typechain-types' import { createRewardSplitterFactory, ethVaultFixture } from './shared/fixtures' import { expect } from './shared/expect' @@ -16,7 +17,11 @@ import snapshotGasCost from './shared/snapshotGasCost' describe('RewardSplitter', () => { let admin: Wallet, other: Wallet - let vault: EthVault, keeper: Keeper, rewardSplitter: RewardSplitter, erc20Vault: EthErc20Vault + let vault: EthVault, + keeper: Keeper, + rewardSplitter: RewardSplitter, + erc20Vault: EthErc20Vault, + depositDataManager: DepositDataManager before('create fixture loader', async () => { ;[admin, other] = (await (ethers as any).getSigners()).slice(1, 3) @@ -47,8 +52,21 @@ describe('RewardSplitter', () => { true ) keeper = fixture.keeper - await collateralizeEthVault(vault, keeper, fixture.validatorsRegistry, admin) - await collateralizeEthVault(erc20Vault, keeper, fixture.validatorsRegistry, admin) + depositDataManager = fixture.depositDataManager + await collateralizeEthVault( + vault, + keeper, + depositDataManager, + admin, + fixture.validatorsRegistry + ) + await collateralizeEthVault( + erc20Vault, + keeper, + depositDataManager, + admin, + fixture.validatorsRegistry + ) const rewardSplitterFactory = await createRewardSplitterFactory() const rewardSplitterAddress = await rewardSplitterFactory diff --git a/test/VaultsRegistry.spec.ts b/test/VaultsRegistry.spec.ts index 7cb92792..cb96788f 100644 --- a/test/VaultsRegistry.spec.ts +++ b/test/VaultsRegistry.spec.ts @@ -39,6 +39,7 @@ describe('VaultsRegistry', () => { fixture.osTokenVaultController, fixture.osTokenConfig, fixture.sharedMevEscrow, + fixture.depositDataManager, EXITING_ASSETS_MIN_DELAY ) }) diff --git a/test/__snapshots__/DepositDataManager.spec.ts.snap b/test/__snapshots__/DepositDataManager.spec.ts.snap new file mode 100644 index 00000000..9a5db4bd --- /dev/null +++ b/test/__snapshots__/DepositDataManager.spec.ts.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DepositDataManager deposit data manager update succeeds 1`] = ` +Object { + "calldataByteLength": 68, + "gasUsed": 62126, +} +`; + +exports[`DepositDataManager deposit data root update success 1`] = ` +Object { + "calldataByteLength": 68, + "gasUsed": 56000, +} +`; + +exports[`DepositDataManager migrate succeeds 1`] = ` +Object { + "calldataByteLength": 100, + "gasUsed": 118756, +} +`; + +exports[`DepositDataManager multiple validators succeeds 1`] = ` +Object { + "calldataByteLength": 3364, + "gasUsed": 684817, +} +`; + +exports[`DepositDataManager single validator succeeds 1`] = ` +Object { + "calldataByteLength": 1156, + "gasUsed": 331930, +} +`; diff --git a/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap b/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap index 1180d9c3..c90ab2e7 100644 --- a/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap @@ -3,7 +3,7 @@ exports[`EthBlocklistErc20Vault deposit can be called by not blocked user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 71181, + "gasUsed": 71093, } `; @@ -17,13 +17,13 @@ Object { exports[`EthBlocklistErc20Vault mint osToken can mint from not blocked user 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 174071, + "gasUsed": 174049, } `; exports[`EthBlocklistErc20Vault transfer can transfer 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 58987, + "gasUsed": 58921, } `; diff --git a/test/__snapshots__/EthBlocklistVault.spec.ts.snap b/test/__snapshots__/EthBlocklistVault.spec.ts.snap index f422fa1e..7f5e8ce5 100644 --- a/test/__snapshots__/EthBlocklistVault.spec.ts.snap +++ b/test/__snapshots__/EthBlocklistVault.spec.ts.snap @@ -3,7 +3,7 @@ exports[`EthBlocklistVault deposit can be called by not blocked user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 69056, + "gasUsed": 68968, } `; @@ -17,6 +17,6 @@ Object { exports[`EthBlocklistVault mint osToken can mint from not blocked user 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 173861, + "gasUsed": 173839, } `; diff --git a/test/__snapshots__/EthErc20Vault.spec.ts.snap b/test/__snapshots__/EthErc20Vault.spec.ts.snap index e4386f4c..06d196d5 100644 --- a/test/__snapshots__/EthErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthErc20Vault.spec.ts.snap @@ -3,21 +3,21 @@ exports[`EthErc20Vault deposit emits transfer event 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 68858, + "gasUsed": 68776, } `; exports[`EthErc20Vault deposit through receive fallback function emits transfer event 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 76116, + "gasUsed": 76122, } `; exports[`EthErc20Vault enter exit queue emits transfer event 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 96285, + "gasUsed": 96269, } `; diff --git a/test/__snapshots__/EthFoxVault.spec.ts.snap b/test/__snapshots__/EthFoxVault.spec.ts.snap index a4d88765..14afb38c 100644 --- a/test/__snapshots__/EthFoxVault.spec.ts.snap +++ b/test/__snapshots__/EthFoxVault.spec.ts.snap @@ -3,21 +3,21 @@ exports[`EthFoxVault blocklist can be updated by blocklist manager 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 53743, + "gasUsed": 53677, } `; exports[`EthFoxVault blocklist can be updated by blocklist manager 2`] = ` Object { "calldataByteLength": 68, - "gasUsed": 31831, + "gasUsed": 31765, } `; exports[`EthFoxVault deposit can be called by not blocked user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 68968, + "gasUsed": 68880, } `; @@ -31,20 +31,20 @@ Object { exports[`EthFoxVault ejecting user blocklist manager can eject all of the user assets for collateralized vault 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 119659, + "gasUsed": 119593, } `; exports[`EthFoxVault ejecting user does not fail for user with no vault shares 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 55791, + "gasUsed": 55725, } `; exports[`EthFoxVault set blocklist manager admin can update blocklist manager 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 35941, + "gasUsed": 35897, } `; diff --git a/test/__snapshots__/EthGenesisVault.spec.ts.snap b/test/__snapshots__/EthGenesisVault.spec.ts.snap index 51309ab0..f54b1e17 100644 --- a/test/__snapshots__/EthGenesisVault.spec.ts.snap +++ b/test/__snapshots__/EthGenesisVault.spec.ts.snap @@ -3,62 +3,62 @@ exports[`EthGenesisVault can deposit through receive fallback function 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 74328, + "gasUsed": 74334, } `; exports[`EthGenesisVault migrate migrates from rewardEthToken 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 79638, + "gasUsed": 79584, } `; exports[`EthGenesisVault pulls withdrawals on claim exited assets 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 77206, + "gasUsed": 77160, } `; exports[`EthGenesisVault pulls withdrawals on multiple validators registration 1`] = ` Object { - "calldataByteLength": 3716, - "gasUsed": 638183, + "calldataByteLength": 3748, + "gasUsed": 649263, } `; exports[`EthGenesisVault pulls withdrawals on single validator registration 1`] = ` Object { - "calldataByteLength": 1124, - "gasUsed": 301781, + "calldataByteLength": 1156, + "gasUsed": 310166, } `; exports[`EthGenesisVault update state deducts rewards on first state update 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 150242, + "gasUsed": 150251, } `; exports[`EthGenesisVault update state skips updating legacy with zero total assets 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 145811, + "gasUsed": 145820, } `; exports[`EthGenesisVault update state splits penalty between rewardEthToken and vault 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 119650, + "gasUsed": 119659, } `; exports[`EthGenesisVault update state splits reward between rewardEthToken and vault 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 167342, + "gasUsed": 167351, } `; diff --git a/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap b/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap index d005196a..0a21bcd2 100644 --- a/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap @@ -3,7 +3,7 @@ exports[`EthPrivErc20Vault deposit can be called by whitelisted user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 71169, + "gasUsed": 71081, } `; @@ -17,13 +17,13 @@ Object { exports[`EthPrivErc20Vault mint osToken can mint from whitelisted user 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 174096, + "gasUsed": 174074, } `; exports[`EthPrivErc20Vault transfer can transfer to whitelisted user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 59015, + "gasUsed": 58949, } `; diff --git a/test/__snapshots__/EthPrivVault.spec.ts.snap b/test/__snapshots__/EthPrivVault.spec.ts.snap index ebdedaf3..60554a49 100644 --- a/test/__snapshots__/EthPrivVault.spec.ts.snap +++ b/test/__snapshots__/EthPrivVault.spec.ts.snap @@ -3,6 +3,6 @@ exports[`EthPrivVault mint osToken can mint from not whitelisted user 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 173886, + "gasUsed": 173864, } `; diff --git a/test/__snapshots__/EthVault.deposit.spec.ts.snap b/test/__snapshots__/EthVault.deposit.spec.ts.snap index 6858829e..188c129d 100644 --- a/test/__snapshots__/EthVault.deposit.spec.ts.snap +++ b/test/__snapshots__/EthVault.deposit.spec.ts.snap @@ -3,27 +3,27 @@ exports[`EthVault - deposit empty vault: no assets & no shares deposit 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 66733, + "gasUsed": 66651, } `; exports[`EthVault - deposit full vault: assets & shares deposit 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 66733, + "gasUsed": 66651, } `; exports[`EthVault - deposit full vault: assets & shares deposit through receive fallback function 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 74288, + "gasUsed": 74294, } `; exports[`EthVault - deposit full vault: assets & shares update state and deposit 1`] = ` Object { "calldataByteLength": 260, - "gasUsed": 155321, + "gasUsed": 155308, } `; diff --git a/test/__snapshots__/EthVault.liquidate.spec.ts.snap b/test/__snapshots__/EthVault.liquidate.spec.ts.snap index cbe7a222..bc58a4e7 100644 --- a/test/__snapshots__/EthVault.liquidate.spec.ts.snap +++ b/test/__snapshots__/EthVault.liquidate.spec.ts.snap @@ -3,13 +3,13 @@ exports[`EthVault - liquidate calculates liquidation correctly 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 109382, + "gasUsed": 109388, } `; exports[`EthVault - liquidate can liquidate 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 109382, + "gasUsed": 109388, } `; diff --git a/test/__snapshots__/EthVault.mint.spec.ts.snap b/test/__snapshots__/EthVault.mint.spec.ts.snap index c3972025..a2151efe 100644 --- a/test/__snapshots__/EthVault.mint.spec.ts.snap +++ b/test/__snapshots__/EthVault.mint.spec.ts.snap @@ -3,13 +3,13 @@ exports[`EthVault - mint mints osTokens to the receiver 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 171574, + "gasUsed": 171564, } `; exports[`EthVault - mint updates position accumulated fee 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 116686, + "gasUsed": 116676, } `; diff --git a/test/__snapshots__/EthVault.multicall.spec.ts.snap b/test/__snapshots__/EthVault.multicall.spec.ts.snap index cd9430e5..4f7d0e4e 100644 --- a/test/__snapshots__/EthVault.multicall.spec.ts.snap +++ b/test/__snapshots__/EthVault.multicall.spec.ts.snap @@ -3,13 +3,13 @@ exports[`EthVault - multicall can update state and queue for exit 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 168451, + "gasUsed": 168343, } `; exports[`EthVault - multicall can update state and queue for exit 2`] = ` Object { "calldataByteLength": 548, - "gasUsed": 109844, + "gasUsed": 109728, } `; diff --git a/test/__snapshots__/EthVault.redeem.spec.ts.snap b/test/__snapshots__/EthVault.redeem.spec.ts.snap index 5390f7e7..7c7e5c0f 100644 --- a/test/__snapshots__/EthVault.redeem.spec.ts.snap +++ b/test/__snapshots__/EthVault.redeem.spec.ts.snap @@ -3,13 +3,13 @@ exports[`EthVault - redeem osToken calculates redeem correctly 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 125543, + "gasUsed": 125549, } `; exports[`EthVault - redeem osToken can redeem 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 125543, + "gasUsed": 125549, } `; diff --git a/test/__snapshots__/EthVault.register.spec.ts.snap b/test/__snapshots__/EthVault.register.spec.ts.snap index 18bd2761..ca7d4fab 100644 --- a/test/__snapshots__/EthVault.register.spec.ts.snap +++ b/test/__snapshots__/EthVault.register.spec.ts.snap @@ -2,14 +2,14 @@ exports[`EthVault - register multiple validators succeeds 1`] = ` Object { - "calldataByteLength": 3332, - "gasUsed": 673669, + "calldataByteLength": 2532, + "gasUsed": 630912, } `; exports[`EthVault - register single validator succeeds 1`] = ` Object { - "calldataByteLength": 1156, - "gasUsed": 324148, + "calldataByteLength": 964, + "gasUsed": 298127, } `; diff --git a/test/__snapshots__/EthVault.settings.spec.ts.snap b/test/__snapshots__/EthVault.settings.spec.ts.snap index 35a28b8d..68ee11d3 100644 --- a/test/__snapshots__/EthVault.settings.spec.ts.snap +++ b/test/__snapshots__/EthVault.settings.spec.ts.snap @@ -3,27 +3,20 @@ exports[`EthVault - settings fee recipient can update 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 44014, + "gasUsed": 43976, } `; exports[`EthVault - settings keys manager can be updated by admin 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 52980, + "gasUsed": 52907, } `; exports[`EthVault - settings metadata IPFS hash only admin can update 1`] = ` Object { "calldataByteLength": 132, - "gasUsed": 32625, -} -`; - -exports[`EthVault - settings validators root can update 1`] = ` -Object { - "calldataByteLength": 36, - "gasUsed": 54908, + "gasUsed": 32559, } `; diff --git a/test/__snapshots__/EthVault.state.spec.ts.snap b/test/__snapshots__/EthVault.state.spec.ts.snap index 6f1db2a9..8dbcf11a 100644 --- a/test/__snapshots__/EthVault.state.spec.ts.snap +++ b/test/__snapshots__/EthVault.state.spec.ts.snap @@ -3,21 +3,21 @@ exports[`EthVault - state allocates fee to recipient when delta is above zero 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 117553, + "gasUsed": 117534, } `; exports[`EthVault - state applies penalty when delta is below zero 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 117338, + "gasUsed": 117319, } `; exports[`EthVault - state splits penalty between exiting assets and staking assets 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 107117, + "gasUsed": 107120, } `; diff --git a/test/__snapshots__/EthVault.token.spec.ts.snap b/test/__snapshots__/EthVault.token.spec.ts.snap index 29a129bd..09221db7 100644 --- a/test/__snapshots__/EthVault.token.spec.ts.snap +++ b/test/__snapshots__/EthVault.token.spec.ts.snap @@ -17,7 +17,7 @@ Object { exports[`EthVault - token permit accepts owner signature 1`] = ` Object { "calldataByteLength": 228, - "gasUsed": 82389, + "gasUsed": 82357, } `; @@ -38,13 +38,13 @@ Object { exports[`EthVault - token transfer when the sender transfers all balance transfers the requested amount 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 54465, + "gasUsed": 54399, } `; exports[`EthVault - token transfer when the sender transfers zero tokens transfers the requested amount 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 36541, + "gasUsed": 36475, } `; diff --git a/test/__snapshots__/EthVault.upgrade.spec.ts.snap b/test/__snapshots__/EthVault.upgrade.spec.ts.snap index 7750f50b..f254c3ce 100644 --- a/test/__snapshots__/EthVault.upgrade.spec.ts.snap +++ b/test/__snapshots__/EthVault.upgrade.spec.ts.snap @@ -3,48 +3,48 @@ exports[`EthVault - upgrade does not modify the state variables 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 82702, + "gasUsed": 162871, } `; exports[`EthVault - upgrade does not modify the state variables 2`] = ` Object { "calldataByteLength": 100, - "gasUsed": 83366, + "gasUsed": 163547, } `; exports[`EthVault - upgrade does not modify the state variables 3`] = ` Object { "calldataByteLength": 100, - "gasUsed": 82922, + "gasUsed": 163103, } `; exports[`EthVault - upgrade does not modify the state variables 4`] = ` Object { "calldataByteLength": 100, - "gasUsed": 83586, + "gasUsed": 163767, } `; exports[`EthVault - upgrade does not modify the state variables 5`] = ` Object { "calldataByteLength": 100, - "gasUsed": 82790, + "gasUsed": 162945, } `; exports[`EthVault - upgrade works with valid call data 1`] = ` Object { "calldataByteLength": 132, - "gasUsed": 76084, + "gasUsed": 76046, } `; exports[`EthVault - upgrade works with valid call data 2`] = ` Object { "calldataByteLength": 132, - "gasUsed": 76084, + "gasUsed": 76046, } `; diff --git a/test/__snapshots__/EthVault.whitelist.spec.ts.snap b/test/__snapshots__/EthVault.whitelist.spec.ts.snap index 0fff6a6c..dce0e713 100644 --- a/test/__snapshots__/EthVault.whitelist.spec.ts.snap +++ b/test/__snapshots__/EthVault.whitelist.spec.ts.snap @@ -3,7 +3,7 @@ exports[`EthVault - whitelist deposit can be called by whitelisted user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 69044, + "gasUsed": 68956, } `; @@ -17,7 +17,7 @@ Object { exports[`EthVault - whitelist set whitelister admin can update whitelister 1`] = ` Object { "calldataByteLength": 36, - "gasUsed": 36223, + "gasUsed": 36135, } `; diff --git a/test/__snapshots__/EthVault.withdraw.spec.ts.snap b/test/__snapshots__/EthVault.withdraw.spec.ts.snap index 9f11d9f4..4561b60a 100644 --- a/test/__snapshots__/EthVault.withdraw.spec.ts.snap +++ b/test/__snapshots__/EthVault.withdraw.spec.ts.snap @@ -3,35 +3,35 @@ exports[`EthVault - withdraw claim exited assets for single user in multiple checkpoints in multiple transactions 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 76025, + "gasUsed": 75973, } `; exports[`EthVault - withdraw claim exited assets for single user in multiple checkpoints in multiple transactions 2`] = ` Object { "calldataByteLength": 100, - "gasUsed": 48843, + "gasUsed": 48801, } `; exports[`EthVault - withdraw claim exited assets for single user in multiple checkpoints in single transaction 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 51461, + "gasUsed": 51409, } `; exports[`EthVault - withdraw claim exited assets for single user in single checkpoint 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 48843, + "gasUsed": 48801, } `; exports[`EthVault - withdraw enter exit queue locks assets for the time of exit 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 77382, + "gasUsed": 77366, } `; diff --git a/test/__snapshots__/EthVaultFactory.spec.ts.snap b/test/__snapshots__/EthVaultFactory.spec.ts.snap index 406947fa..68bd70d0 100644 --- a/test/__snapshots__/EthVaultFactory.spec.ts.snap +++ b/test/__snapshots__/EthVaultFactory.spec.ts.snap @@ -17,14 +17,14 @@ Object { exports[`EthVaultFactory EthErc20Vault public vault deployment with own escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 553267, + "gasUsed": 553279, } `; exports[`EthVaultFactory EthErc20Vault public vault deployment with shared escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 404448, + "gasUsed": 404460, } `; @@ -45,13 +45,13 @@ Object { exports[`EthVaultFactory EthVault public vault deployment with own escrow gas 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 479145, + "gasUsed": 479157, } `; exports[`EthVaultFactory EthVault public vault deployment with shared escrow gas 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 330326, + "gasUsed": 330338, } `; diff --git a/test/__snapshots__/KeeperRewards.spec.ts.snap b/test/__snapshots__/KeeperRewards.spec.ts.snap index 84165ae3..1e20d73d 100644 --- a/test/__snapshots__/KeeperRewards.spec.ts.snap +++ b/test/__snapshots__/KeeperRewards.spec.ts.snap @@ -2,43 +2,43 @@ exports[`KeeperRewards harvest (own escrow) succeeds for latest rewards root 1`] = ` Object { - "calldataByteLength": 324, - "gasUsed": 111374, + "calldataByteLength": 292, + "gasUsed": 110652, } `; exports[`KeeperRewards harvest (own escrow) succeeds for previous rewards root 1`] = ` Object { - "calldataByteLength": 324, - "gasUsed": 113528, + "calldataByteLength": 292, + "gasUsed": 112806, } `; exports[`KeeperRewards harvest (own escrow) succeeds for previous rewards root 2`] = ` Object { "calldataByteLength": 196, - "gasUsed": 74295, + "gasUsed": 74286, } `; exports[`KeeperRewards harvest (shared escrow) succeeds for latest rewards root 1`] = ` Object { - "calldataByteLength": 292, - "gasUsed": 143928, + "calldataByteLength": 324, + "gasUsed": 144655, } `; exports[`KeeperRewards harvest (shared escrow) succeeds for previous rewards root 1`] = ` Object { - "calldataByteLength": 292, - "gasUsed": 146082, + "calldataByteLength": 324, + "gasUsed": 146809, } `; exports[`KeeperRewards harvest (shared escrow) succeeds for previous rewards root 2`] = ` Object { "calldataByteLength": 196, - "gasUsed": 90462, + "gasUsed": 90443, } `; @@ -59,13 +59,13 @@ Object { exports[`KeeperRewards update rewards succeeds 2`] = ` Object { "calldataByteLength": 772, - "gasUsed": 123627, + "gasUsed": 123639, } `; exports[`KeeperRewards update rewards succeeds with all signatures 1`] = ` Object { "calldataByteLength": 1156, - "gasUsed": 146931, + "gasUsed": 146943, } `; diff --git a/test/__snapshots__/KeeperValidators.spec.ts.snap b/test/__snapshots__/KeeperValidators.spec.ts.snap index 426a2016..45dd8b5d 100644 --- a/test/__snapshots__/KeeperValidators.spec.ts.snap +++ b/test/__snapshots__/KeeperValidators.spec.ts.snap @@ -2,29 +2,29 @@ exports[`KeeperValidators register multiple validators succeeds 1`] = ` Object { - "calldataByteLength": 3716, - "gasUsed": 679986, + "calldataByteLength": 3748, + "gasUsed": 691222, } `; exports[`KeeperValidators register multiple validators succeeds 2`] = ` Object { - "calldataByteLength": 3716, - "gasUsed": 588392, + "calldataByteLength": 3748, + "gasUsed": 599466, } `; exports[`KeeperValidators register single validator succeeds 1`] = ` Object { "calldataByteLength": 1540, - "gasUsed": 330512, + "gasUsed": 338356, } `; exports[`KeeperValidators register single validator succeeds 2`] = ` Object { - "calldataByteLength": 1508, - "gasUsed": 276240, + "calldataByteLength": 1540, + "gasUsed": 284631, } `; diff --git a/test/__snapshots__/RewardSplitter.spec.ts.snap b/test/__snapshots__/RewardSplitter.spec.ts.snap index f2cc9f02..75364a22 100644 --- a/test/__snapshots__/RewardSplitter.spec.ts.snap +++ b/test/__snapshots__/RewardSplitter.spec.ts.snap @@ -3,7 +3,7 @@ exports[`RewardSplitter decrease shares owner can decrease shares 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 67101, + "gasUsed": 67019, } `; @@ -17,20 +17,20 @@ Object { exports[`RewardSplitter sync rewards anyone can sync rewards 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 73126, + "gasUsed": 73044, } `; exports[`RewardSplitter withdraw rewards can claim vault tokens for ERC-20 vault 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 78180, + "gasUsed": 78114, } `; exports[`RewardSplitter withdraw rewards can enter exit queue with multicall 1`] = ` Object { "calldataByteLength": 612, - "gasUsed": 170884, + "gasUsed": 170789, } `; diff --git a/test/__snapshots__/VaultsRegistry.spec.ts.snap b/test/__snapshots__/VaultsRegistry.spec.ts.snap index da8cf135..ab82aafd 100644 --- a/test/__snapshots__/VaultsRegistry.spec.ts.snap +++ b/test/__snapshots__/VaultsRegistry.spec.ts.snap @@ -3,7 +3,7 @@ exports[`VaultsRegistry factory can add vault 1`] = ` Object { "calldataByteLength": 324, - "gasUsed": 330326, + "gasUsed": 330338, } `; diff --git a/test/shared/fixtures.ts b/test/shared/fixtures.ts index d76c3751..39da306b 100644 --- a/test/shared/fixtures.ts +++ b/test/shared/fixtures.ts @@ -51,6 +51,8 @@ import { SharedMevEscrow__factory, VaultsRegistry, VaultsRegistry__factory, + DepositDataManager, + DepositDataManager__factory, } from '../../typechain-types' import { getEthValidatorsRegistryFactory } from './contracts' import { @@ -239,6 +241,15 @@ export const createRewardSplitterFactory = async function (): Promise { + const signer = await ethers.provider.getSigner() + const factory = await ethers.getContractFactory('DepositDataManager') + const contract = await factory.deploy(await vaultsRegistry.getAddress()) + return DepositDataManager__factory.connect(await contract.getAddress(), signer) +} + export const createOsTokenVaultController = async function ( keeperAddress: string, registry: VaultsRegistry, @@ -399,6 +410,7 @@ export const deployEthGenesisVaultImpl = async function ( osTokenVaultController: OsTokenVaultController, osTokenConfig: OsTokenConfig, sharedMevEscrow: SharedMevEscrow, + depositDataManager: DepositDataManager, poolEscrow: PoolEscrowMock, rewardEthToken: LegacyRewardTokenMock ): Promise { @@ -410,6 +422,7 @@ export const deployEthGenesisVaultImpl = async function ( await osTokenVaultController.getAddress(), await osTokenConfig.getAddress(), await sharedMevEscrow.getAddress(), + await depositDataManager.getAddress(), await poolEscrow.getAddress(), await rewardEthToken.getAddress(), EXITING_ASSETS_MIN_DELAY, @@ -428,6 +441,7 @@ export const deployEthVaultImplementation = async function ( osTokenVaultController: OsTokenVaultController, osTokenConfig: OsTokenConfig, sharedMevEscrow: SharedMevEscrow, + depositDataManager: DepositDataManager, exitingAssetsMinDelay: number ): Promise { const factory = await ethers.getContractFactory(vaultType) @@ -438,6 +452,7 @@ export const deployEthVaultImplementation = async function ( await osTokenVaultController.getAddress(), await osTokenConfig.getAddress(), await sharedMevEscrow.getAddress(), + await depositDataManager.getAddress(), exitingAssetsMinDelay, ] const contract = await factory.deploy(...constructorArgs) @@ -540,6 +555,7 @@ interface EthVaultFixture { vaultsRegistry: VaultsRegistry keeper: Keeper sharedMevEscrow: SharedMevEscrow + depositDataManager: DepositDataManager validatorsRegistry: Contract ethVaultFactory: EthVaultFactory ethPrivVaultFactory: EthVaultFactory @@ -672,7 +688,10 @@ export const ethVaultFixture = async function (): Promise { OSTOKEN_LTV ) - // 7. deploy implementations and factories + // 7. deploy depositDataManager + const depositDataManager = await createDepositDataManager(vaultsRegistry) + + // 8. deploy implementations and factories const factories = {} const implementations = {} @@ -693,6 +712,7 @@ export const ethVaultFixture = async function (): Promise { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, EXITING_ASSETS_MIN_DELAY ) await vaultsRegistry.addVaultImpl(vaultImpl) @@ -717,6 +737,7 @@ export const ethVaultFixture = async function (): Promise { return { vaultsRegistry, sharedMevEscrow, + depositDataManager, keeper, validatorsRegistry, ethVaultFactory, @@ -764,6 +785,7 @@ export const ethVaultFixture = async function (): Promise { await vaultsRegistry.getAddress(), await validatorsRegistry.getAddress(), await sharedMevEscrow.getAddress(), + await depositDataManager.getAddress(), EXITING_ASSETS_MIN_DELAY, ] const contract = await factory.deploy(...constructorArgs) @@ -951,6 +973,7 @@ export const ethVaultFixture = async function (): Promise { osTokenVaultController, osTokenConfig, sharedMevEscrow, + depositDataManager, poolEscrow, rewardEthToken ) diff --git a/test/shared/rewards.ts b/test/shared/rewards.ts index 404de1ae..555f5c54 100644 --- a/test/shared/rewards.ts +++ b/test/shared/rewards.ts @@ -1,7 +1,7 @@ import { ethers, network } from 'hardhat' import { Contract, Signer } from 'ethers' import { StandardMerkleTree } from '@openzeppelin/merkle-tree' -import { EthVault, IKeeperRewards, Keeper } from '../../typechain-types' +import { EthVault, IKeeperRewards, Keeper, DepositDataManager } from '../../typechain-types' import { EIP712Domain, EXITING_ASSETS_MIN_DELAY, @@ -149,7 +149,7 @@ export async function collateralizeEthV1Vault( .connect(admin) .deposit(adminAddr, ZERO_ADDRESS, { value: validatorDeposit }) const shares = await extractDepositShares(tx) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, null, admin, validatorsRegistry) // update rewards tree const vaultReward = getHarvestParams(vaultAddress, 0n, 0n) @@ -182,8 +182,9 @@ export async function collateralizeEthV1Vault( export async function collateralizeEthVault( vault: EthVaultType, keeper: Keeper, - validatorsRegistry: Contract, - admin: Signer + depositDataManager: DepositDataManager, + admin: Signer, + validatorsRegistry: Contract ) { const vaultAddress = await vault.getAddress() const balanceBefore = await ethers.provider.getBalance(vaultAddress) @@ -195,7 +196,7 @@ export async function collateralizeEthVault( .connect(admin) .deposit(adminAddr, ZERO_ADDRESS, { value: validatorDeposit }) const receivedShares = await extractDepositShares(tx) - await registerEthValidator(vault, keeper, validatorsRegistry, admin) + await registerEthValidator(vault, keeper, depositDataManager, admin, validatorsRegistry) // exit validator const response = await vault.connect(admin).enterExitQueue(receivedShares, adminAddr) diff --git a/test/shared/validators.ts b/test/shared/validators.ts index 0bc9aa99..d3323d32 100644 --- a/test/shared/validators.ts +++ b/test/shared/validators.ts @@ -4,13 +4,14 @@ import { ethers, network } from 'hardhat' import { Buffer } from 'buffer' import { BytesLike, Contract, ContractTransactionResponse, Signer } from 'ethers' import bls from 'bls-eth-wasm' -import { EthVault, Keeper } from '../../typechain-types' +import { EthVault, Keeper, DepositDataManager } from '../../typechain-types' import { EIP712Domain, KeeperUpdateExitSignaturesSig, KeeperValidatorsSig, VALIDATORS_DEADLINE, VALIDATORS_MIN_ORACLES, + ZERO_BYTES32, } from './constants' import { getOraclesSignatures } from './fixtures' import { EthVaultType } from './types' @@ -284,12 +285,22 @@ export function getValidatorsMultiProof( export async function registerEthValidator( vault: EthVaultType, keeper: Keeper, - validatorsRegistry: Contract, - admin: Signer + depositDataManager: DepositDataManager, + admin: Signer, + validatorsRegistry: Contract ): Promise { const validatorsData = await createEthValidatorsData(vault) const validatorsRegistryRoot = await validatorsRegistry.get_deposit_root() - await vault.connect(admin).setValidatorsRoot(validatorsData.root) + const vaultAddress = await vault.getAddress() + if ((await vault.version()) > 1) { + if ((await depositDataManager.depositDataRoots(vaultAddress)) != ZERO_BYTES32) { + // reset validator index + await depositDataManager.connect(admin).setDepositDataRoot(vaultAddress, ZERO_BYTES32) + } + await depositDataManager.connect(admin).setDepositDataRoot(vaultAddress, validatorsData.root) + } else { + await vault.connect(admin).setValidatorsRoot(validatorsData.root) + } const validator = validatorsData.validators[0] const exitSignatureIpfsHash = exitSignatureIpfsHashes[0] const signingData = await getEthValidatorsSigningData( @@ -302,14 +313,28 @@ export async function registerEthValidator( ) const signatures = getOraclesSignatures(signingData, VALIDATORS_MIN_ORACLES) const proof = getValidatorProof(validatorsData.tree, validator, 0) - return await vault.registerValidator( - { - validatorsRegistryRoot, - validators: validator, - signatures, - exitSignaturesIpfsHash: exitSignatureIpfsHash, - deadline: VALIDATORS_DEADLINE, - }, - proof - ) + if ((await vault.version()) > 1) { + return await depositDataManager.connect(admin).registerValidator( + vaultAddress, + { + validatorsRegistryRoot, + validators: validator, + signatures, + exitSignaturesIpfsHash: exitSignatureIpfsHash, + deadline: VALIDATORS_DEADLINE, + }, + proof + ) + } else { + return await vault.registerValidator( + { + validatorsRegistryRoot, + validators: validator, + signatures, + exitSignaturesIpfsHash: exitSignatureIpfsHash, + deadline: VALIDATORS_DEADLINE, + }, + proof + ) + } }