diff --git a/.gas-snapshot b/.gas-snapshot index 2eb794a3..24dcc6d5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -6,94 +6,95 @@ EnclaveVerifierTest:testRaveEvidence3() (gas: 901150) EnclaveVerifierTest:testRemoveLeafX509() (gas: 200998) EnclaveVerifierTest:testSetup() (gas: 89268) EnclaveVerifierTest:testVerifyingStaleEvidence() (gas: 739311) -GuardianModuleTest:testAddGuardian(address) (runs: 256, μ: 133546, ~: 133546) -GuardianModuleTest:testChangeThreshold() (gas: 35408) -GuardianModuleTest:testChangeThresholdReverts() (gas: 27725) +GuardianModuleTest:testAddGuardian(address) (runs: 256, μ: 133524, ~: 133524) +GuardianModuleTest:testChangeThreshold() (gas: 35386) +GuardianModuleTest:testChangeThresholdReverts() (gas: 27703) GuardianModuleTest:testPostWithdrawalsRootReverts(address[],uint256[]) (runs: 256, μ: 84793, ~: 86424) -GuardianModuleTest:testRave() (gas: 69851395) -GuardianModuleTest:testRemoveGuardian(address) (runs: 256, μ: 113417, ~: 113402) +GuardianModuleTest:testRave() (gas: 69921368) +GuardianModuleTest:testRemoveGuardian(address) (runs: 256, μ: 113383, ~: 113367) GuardianModuleTest:testRotateGuardianKeyFromNonGuardianReverts() (gas: 12201) GuardianModuleTest:testRotateGuardianKeyWithInvalidRaveReverts() (gas: 843560) GuardianModuleTest:testRotateGuardianToInvalidPubKeyReverts() (gas: 14683) GuardianModuleTest:testSplitFunds() (gas: 125793) GuardianModuleTest:testSplitFundsRounding() (gas: 130017) GuardianModuleTest:testValidateSkipProvisioningReverts() (gas: 29704) -NoRestakingModuleTest:testClaimingMultipleProofs() (gas: 250743) -NoRestakingModuleTest:testCollectRewards() (gas: 255017) -NoRestakingModuleTest:testClaimingMultipleProofs() (gas: 250743) -NoRestakingModuleTest:testCollectRewards() (gas: 255017) +LibBeaconChainTest:testUpdatedVersion(bytes32) (runs: 256, μ: 24088, ~: 24088) +NoRestakingModuleTest:testClaimingMultipleProofs() (gas: 250655) +NoRestakingModuleTest:testCollectRewards() (gas: 254929) +NoRestakingModuleTest:testCollectRewardsRevertsForZeroValues() (gas: 28228) NoRestakingModuleTest:testDonation() (gas: 11855) -NoRestakingModuleTest:testDoubleClaimInSameTransaction() (gas: 196261) -NoRestakingModuleTest:testPostRewardsRoot(bytes32,uint256) (runs: 256, μ: 103892, ~: 103892) -NoRestakingModuleTest:testPostRewardsRootReverts(address,bytes32,uint256) (runs: 256, μ: 42051, ~: 42585) -NoRestakingModuleTest:testPostingRewardsForSameBlockReverts() (gas: 106293) -NoRestakingModuleTest:testRewardsClaimingForAnotherUser(address) (runs: 256, μ: 264137, ~: 264245) +NoRestakingModuleTest:testDoubleClaimInSameTransaction() (gas: 196173) +NoRestakingModuleTest:testPostRewardsRoot(bytes32,uint256) (runs: 256, μ: 103848, ~: 103848) +NoRestakingModuleTest:testPostRewardsRootReverts(address,bytes32,uint256) (runs: 256, μ: 41986, ~: 42519) +NoRestakingModuleTest:testPostingRewardsForSameBlockReverts() (gas: 106249) +NoRestakingModuleTest:testRewardsClaimingForAnotherUser(address) (runs: 256, μ: 264135, ~: 264135) NoRestakingModuleTest:testSetup() (gas: 16008) PufferModuleTest:testBeaconUpgrade() (gas: 379702) -PufferModuleTest:testCollectNoRestakingRewards(bytes32) (runs: 256, μ: 1223217, ~: 1223217) +PufferModuleTest:testCollectNoRestakingRewards(bytes32) (runs: 256, μ: 1223173, ~: 1223173) PufferModuleTest:testCreatePufferModule(bytes32) (runs: 256, μ: 303410, ~: 303410) PufferModuleTest:testDonation(bytes32) (runs: 256, μ: 309589, ~: 309589) -PufferModuleTest:testPostRewardsRoot(bytes32,uint256) (runs: 256, μ: 395216, ~: 395216) -PufferModuleTest:testPostRewardsRootReverts(bytes32,address,bytes32,uint256) (runs: 256, μ: 328211, ~: 328900) +PufferModuleTest:testPostRewardsRoot(bytes32,uint256) (runs: 256, μ: 395172, ~: 395172) +PufferModuleTest:testPostRewardsRootReverts(bytes32,address,bytes32,uint256) (runs: 256, μ: 328147, ~: 328834) PufferPoolIntegrationTest:testMulticallStrategyDepositOnGoerli() (gas: 302738) -PufferPoolTest:testBurn(address) (runs: 256, μ: 104774, ~: 104758) -PufferPoolTest:testDeposit(address,uint256) (runs: 256, μ: 145087, ~: 145207) -PufferPoolTest:testDepositAndRedeemRoundingError(address,uint256) (runs: 256, μ: 189766, ~: 189824) -PufferPoolTest:testDepositAndRedeemRoundingErrorForDifferentExchangeRate(address,uint256) (runs: 256, μ: 323869, ~: 323986) +PufferPoolTest:testBurn(address) (runs: 256, μ: 104773, ~: 104758) +PufferPoolTest:testDeposit(address,uint256) (runs: 256, μ: 145055, ~: 145172) +PufferPoolTest:testDepositAndRedeemRoundingError(address,uint256) (runs: 256, μ: 189737, ~: 189797) +PufferPoolTest:testDepositAndRedeemRoundingErrorForDifferentExchangeRate(address,uint256) (runs: 256, μ: 323830, ~: 323947) PufferPoolTest:testDepositForOneWei() (gas: 89277) PufferPoolTest:testMintZero() (gas: 20152) PufferPoolTest:testMultipleDeposits() (gas: 138195) -PufferPoolTest:testRatioChange() (gas: 230870) -PufferPoolTest:testRatioChangeSandwichAttack(uint256,uint256) (runs: 256, μ: 207776, ~: 207954) +PufferPoolTest:testRatioChange() (gas: 230866) +PufferPoolTest:testRatioChangeSandwichAttack(uint256,uint256) (runs: 256, μ: 207753, ~: 207919) PufferPoolTest:testRecoverERC20() (gas: 516930) PufferPoolTest:testSetup() (gas: 32675) PufferPoolTest:testStorageS() (gas: 19397) -PufferProtocolInvariants:invariant_callSummary() (runs: 100, calls: 11000, reverts: 1) +PufferProtocolInvariants:invariant_callSummary() (runs: 100, calls: 11000, reverts: 0) PufferProtocolInvariants:invariant_pufEthToETHRate() (runs: 100, calls: 11000, reverts: 0) -PufferProtocolInvariants:invariant_pufferPoolETHCanOnlyGoUp() (runs: 100, calls: 11000, reverts: 9) -PufferProtocolInvariants:invariant_pufferProtocolBond() (runs: 100, calls: 11000, reverts: 0) -PufferProtocolTest:testBurstThreshold() (gas: 542477) -PufferProtocolTest:testChangeModule() (gas: 30532) -PufferProtocolTest:testChangeModuleToCustom() (gas: 51226) -PufferProtocolTest:testClaimBackBond() (gas: 2306577) -PufferProtocolTest:testClaimBackBondForSingleWithdrawal() (gas: 1181426) -PufferProtocolTest:testCreateExistingModuleShouldFail() (gas: 33461) -PufferProtocolTest:testCreatePufferModule() (gas: 298009) -PufferProtocolTest:testEmptyQueue() (gas: 32549) -PufferProtocolTest:testExtendCommitment() (gas: 527638) -PufferProtocolTest:testFeeCalculations() (gas: 134767) -PufferProtocolTest:testFeeCalculationsEverythingToPufferPool() (gas: 145527) -PufferProtocolTest:testFeeCalculationsEverythingToWithdrawalPool() (gas: 147361) -PufferProtocolTest:testFuzzRegisterManyValidators(uint8) (runs: 256, μ: 26842065, ~: 14413412) -PufferProtocolTest:testGetPayload() (gas: 143359) -PufferProtocolTest:testModuleDOS() (gas: 3640730) -PufferProtocolTest:testPause() (gas: 135124) -PufferProtocolTest:testProofOfReserve() (gas: 176869) -PufferProtocolTest:testProvisionNode() (gas: 3841718) -PufferProtocolTest:testProvisioning() (gas: 61529) +PufferProtocolInvariants:invariant_pufferPoolETHCanOnlyGoUp() (runs: 100, calls: 11000, reverts: 0) +PufferProtocolInvariants:invariant_pufferProtocolBond() (runs: 100, calls: 11000, reverts: 3) +PufferProtocolTest:testBurstThreshold() (gas: 542429) +PufferProtocolTest:testChangeModule() (gas: 30488) +PufferProtocolTest:testChangeModuleToCustom() (gas: 51182) +PufferProtocolTest:testClaimBackBond() (gas: 2306995) +PufferProtocolTest:testClaimBackBondForSingleWithdrawal() (gas: 1181519) +PufferProtocolTest:testCreateExistingModuleShouldFail() (gas: 33439) +PufferProtocolTest:testCreatePufferModule() (gas: 297987) +PufferProtocolTest:testEmptyQueue() (gas: 32527) +PufferProtocolTest:testExtendCommitment() (gas: 527528) +PufferProtocolTest:testFeeCalculations() (gas: 134657) +PufferProtocolTest:testFeeCalculationsEverythingToPufferPool() (gas: 145417) +PufferProtocolTest:testFeeCalculationsEverythingToWithdrawalPool() (gas: 147229) +PufferProtocolTest:testFuzzRegisterManyValidators(uint8) (runs: 256, μ: 25827721, ~: 14412279) +PufferProtocolTest:testGetPayload() (gas: 143448) +PufferProtocolTest:testModuleDOS() (gas: 3640642) +PufferProtocolTest:testPause() (gas: 135102) +PufferProtocolTest:testProofOfReserve() (gas: 176865) +PufferProtocolTest:testProvisionNode() (gas: 3842249) +PufferProtocolTest:testProvisioning() (gas: 61485) PufferProtocolTest:testRegisterInvalidPrivKeyShares() (gas: 77957) -PufferProtocolTest:testRegisterInvalidPubKeyShares() (gas: 81112) -PufferProtocolTest:testRegisterMoreValidatorsThanTheLimit() (gas: 782165) -PufferProtocolTest:testRegisterMultipleValidatorKeysAndDequeue(bytes32,bytes32) (runs: 256, μ: 2044673, ~: 2044673) -PufferProtocolTest:testRegisterToInvalidModule() (gas: 70400) -PufferProtocolTest:testRegisterValidatorKeyWithPermit() (gas: 497556) -PufferProtocolTest:testRegisterValidatorKeyWithPermitSignature() (gas: 506646) -PufferProtocolTest:testRegisterValidatorKeyWithPermitSignatureRevertsInvalidSC() (gas: 224544) -PufferProtocolTest:testRegisterValidatorWithHugeCommitment() (gas: 83628) -PufferProtocolTest:testRegisterWithInvalidAmountPaid() (gas: 86118) -PufferProtocolTest:testRegisterWithInvalidBLSPubKey() (gas: 47089) -PufferProtocolTest:testRegisterWithoutRAVE() (gas: 336964) -PufferProtocolTest:testSetGuardiansFeeRateOverTheLimit() (gas: 28330) -PufferProtocolTest:testSetProtocolFeeRate() (gas: 33527) -PufferProtocolTest:testSetProtocolFeeRateOverTheLimit() (gas: 28343) +PufferProtocolTest:testRegisterInvalidPubKeyShares() (gas: 81090) +PufferProtocolTest:testRegisterMoreValidatorsThanTheLimit() (gas: 782341) +PufferProtocolTest:testRegisterMultipleValidatorKeysAndDequeue(bytes32,bytes32) (runs: 256, μ: 2044772, ~: 2044772) +PufferProtocolTest:testRegisterToInvalidModule() (gas: 70378) +PufferProtocolTest:testRegisterValidatorKeyWithPermit() (gas: 497588) +PufferProtocolTest:testRegisterValidatorKeyWithPermitSignature() (gas: 506678) +PufferProtocolTest:testRegisterValidatorKeyWithPermitSignatureRevertsInvalidSC() (gas: 224665) +PufferProtocolTest:testRegisterValidatorWithHugeCommitment() (gas: 83606) +PufferProtocolTest:testRegisterWithInvalidAmountPaid() (gas: 86207) +PufferProtocolTest:testRegisterWithInvalidBLSPubKey() (gas: 47067) +PufferProtocolTest:testRegisterWithoutRAVE() (gas: 336942) +PufferProtocolTest:testSetGuardiansFeeRateOverTheLimit() (gas: 28419) +PufferProtocolTest:testSetProtocolFeeRate() (gas: 33505) +PufferProtocolTest:testSetProtocolFeeRateOverTheLimit() (gas: 28321) PufferProtocolTest:testSetSmoothingCommitment() (gas: 86893) -PufferProtocolTest:testSetSmoothingCommitment(bytes32) (runs: 256, μ: 86845, ~: 86821) -PufferProtocolTest:testSetWithdrawalPoolRateMax() (gas: 36394) -PufferProtocolTest:testSetup() (gas: 18955) -PufferProtocolTest:testSkipProvisioning() (gas: 993052) -PufferProtocolTest:testStopRegistration() (gas: 1059107) -PufferProtocolTest:testUpgrade() (gas: 4733802) -PufferProtocolTest:testValidatorLimitPerModule() (gas: 500077) -WithdrawalPoolTest:testWithdrawETH() (gas: 183727) -WithdrawalPoolTest:testWithdrawETHWithSignature() (gas: 195607) +PufferProtocolTest:testSetSmoothingCommitment(bytes32) (runs: 256, μ: 86826, ~: 86804) +PufferProtocolTest:testSetWithdrawalPoolRateMax() (gas: 36372) +PufferProtocolTest:testSetup() (gas: 18933) +PufferProtocolTest:testSkipProvisioning() (gas: 993061) +PufferProtocolTest:testStopRegistration() (gas: 1059256) +PufferProtocolTest:testUpgrade() (gas: 4737990) +PufferProtocolTest:testValidatorGriefingAttack() (gas: 951773) +PufferProtocolTest:testValidatorLimitPerModule() (gas: 500033) +WithdrawalPoolTest:testWithdrawETH() (gas: 183692) +WithdrawalPoolTest:testWithdrawETHWithSignature() (gas: 195572) WithdrawalPoolTest:testWithdrawSomebodyElsesPufETHReverts() (gas: 195887) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88f969af..3d27f812 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,10 @@ jobs: tests: runs-on: ubuntu-latest steps: + - uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.SSH_KEY_PUF }} + - uses: actions/checkout@v4 - name: Install Foundry @@ -29,17 +33,19 @@ jobs: slither: runs-on: ubuntu-latest steps: + - uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.SSH_KEY_PUF }} + - uses: actions/checkout@v4 - uses: crytic/slither-action@v0.3.0 - with: - node-version: 16 solhint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 - name: Install solhint run: npm i -g solhint - name: Run solhint diff --git a/.gitmodules b/.gitmodules index 615d49e9..95ccd333 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "lib/murky"] path = lib/murky url = https://github.com/dmfxyz/murky +[submodule "lib/pufETH"] + path = lib/pufETH + url = https://github.com/PufferFinance/pufETH diff --git a/foundry.toml b/foundry.toml index 9758b17f..84b0b686 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,6 +22,7 @@ cbor_metadata = false bytecode_hash = "none" optimizer = true optimizer_runs = 200 +# seed = "0x1337" # uncomment / change when debugging fuzz tests # via_ir = true solc = "0.8.23" # evm_version = "shanghai" diff --git a/lib/pufETH b/lib/pufETH new file mode 160000 index 00000000..f04b4a6d --- /dev/null +++ b/lib/pufETH @@ -0,0 +1 @@ +Subproject commit f04b4a6d6d78d88ad605fb3c2763420bdbf9d87c diff --git a/output/31337-guardians.json b/output/31337-guardians.json index d11f0dab..3fd14f93 100644 --- a/output/31337-guardians.json +++ b/output/31337-guardians.json @@ -1,8 +1,15 @@ { "": "", - "enclaveVerifier": "0xd60DeE1b240f14A4bd22aeA4363D08FA6dA40F25", - "guardianModule": "0xf55C5aa17a2a985316639882F3735Ee103737da5", - "guardians": "0x76d1b66f91044A909C7A3BB834bd302840cd3E4a", - "safeImplementation": "0xc54e6F16A1940d000C6ADEE341D11570490a86F6", - "safeProxyFactory": "0xE25E53220Df33D41b0422FeDBD2D23A94045cFf3" + "PufferDepositor": "0x4E166a233cEeb916B4539afC7922fd8622716552", + "PufferDepositorImplementation": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", + "PufferProtocol": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042", + "PufferProtocolImplementation": "0xf5059a5D33d5853360D16C683c16e67980206f36", + "PufferVault": "0x8411199Ce709DD578a946c09136b7e6b7Fe55824", + "PufferVaultImplementation": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", + "accessManager": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF", + "enclaveVerifier": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "guardianModule": "0xFD471836031dc5108809D173A067e8486B9047A3", + "moduleBeacon": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8", + "moduleFactory": "0x851356ae760d987E095750cCeb3bC6014560891C", + "noRestakingModule": "0x998abeb3E57409262aE5b751f60747921B33613E" } \ No newline at end of file diff --git a/output/puffer.json b/output/puffer.json index a9d3f4bd..f90ef05b 100644 --- a/output/puffer.json +++ b/output/puffer.json @@ -1,14 +1,15 @@ { "": "", - "PufferProtocol": "0x773559Ee80eDE685FBBd5F0Ebd60608DF51b777D", - "PufferProtocolImplementation": "0xE6D876EA7c4B41a2c86DFAFb2E162c686CC95Fb5", - "accessManager": "0x482CDc9277Ae7d4636c0EbA7342C8d440fE7E442", - "enclaveVerifier": "0xB3fAb5f8FDce34FeEf691ccCE6ed20E6b3a0B6fB", - "guardianModule": "0xd4c8730F555F9E9d969BC37280805104c1B039A1", - "moduleBeacon": "0x1BB06aA4c8C8627adb352Dd8C01E38c0482C1c6A", - "moduleFactory": "0x5cd853e676BC218Ec78e4CB904b7dF58db50b8e4", - "noRestakingModule": "0x49CE199bbA75926ab5C6fc16fEDD11d418cB2EDf", - "pauser": "0xeb1D5EE982DbEFf6E57e5Ca3CA45F35bBE229FFE", - "pufferPool": "0xfE7e307d24cB0953b4b5A71E780d6f622525638c", - "withdrawalPool": "0xDAb95f41709473d55EBF7c3b5873b96149A14353" + "PufferDepositor": "0x4E166a233cEeb916B4539afC7922fd8622716552", + "PufferDepositorImplementation": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", + "PufferProtocol": "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7", + "PufferProtocolImplementation": "0x172076E0166D1F9Cc711C77Adf8488051744980C", + "PufferVault": "0x8411199Ce709DD578a946c09136b7e6b7Fe55824", + "PufferVaultImplementation": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", + "accessManager": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF", + "enclaveVerifier": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "guardianModule": "0xFD471836031dc5108809D173A067e8486B9047A3", + "moduleBeacon": "0x202CCe504e04bEd6fC0521238dDf04Bc9E8E15aB", + "moduleFactory": "0xf4B146FbA71F41E0592668ffbF264F1D186b2Ca8", + "noRestakingModule": "0xBEc49fA140aCaA83533fB00A2BB19bDdd0290f25" } \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index 220039ba..1c113281 100644 --- a/remappings.txt +++ b/remappings.txt @@ -14,3 +14,6 @@ solady/=lib/solady/src/ rave/=lib/rave/src/ rave-test/=lib/rave/test/ murky/=lib/murky/src/ +pufETH/=lib/pufETH/src/ +pufETHTest/=lib/pufETH/test/ +pufETHScript/=lib/pufETH/script/ \ No newline at end of file diff --git a/script/DeployEverything.s.sol b/script/DeployEverything.s.sol index 7f476be9..3a11cdac 100644 --- a/script/DeployEverything.s.sol +++ b/script/DeployEverything.s.sol @@ -5,16 +5,46 @@ import { BaseScript } from "script/BaseScript.s.sol"; import { DeployGuardians } from "script/DeployGuardians.s.sol"; import { DeployPuffer } from "script/DeployPuffer.s.sol"; import { SetupAccess } from "script/SetupAccess.s.sol"; -import { GuardiansDeployment, PufferDeployment } from "./DeploymentStructs.sol"; +import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +import { DeployPuffETH, PufferDeployment } from "pufETHScript/DeployPuffETH.s.sol"; +import { UpgradePuffETH } from "pufETHScript/UpgradePuffETH.s.sol"; +import { DeployPufferOracle } from "script/DeployPufferOracle.s.sol"; +import { GuardiansDeployment, PufferProtocolDeployment } from "./DeploymentStructs.sol"; contract DeployEverything is BaseScript { address DAO; - function run(address[] calldata guardians, uint256 threshold) public returns (PufferDeployment memory) { - // Deploy guardians - GuardiansDeployment memory guardiansDeployment = new DeployGuardians().run(guardians, threshold); + function run(address[] calldata guardians, uint256 threshold) public returns (PufferProtocolDeployment memory) { + PufferProtocolDeployment memory deployment; - PufferDeployment memory pufferDeployment = new DeployPuffer().run(guardiansDeployment); + // 1. Deploy pufETH + // @todo In test environment, we need to deploy pufETH first, in prod, we just do the upgrade + // AccessManager is part of the pufETH deployment + PufferDeployment memory puffETHDeployment = new DeployPuffETH().run(); + + deployment.pufferVault = puffETHDeployment.pufferVault; + deployment.pufferDepositor = puffETHDeployment.pufferDepositor; + deployment.stETH = puffETHDeployment.stETH; + deployment.weth = puffETHDeployment.weth; + deployment.accessManager = puffETHDeployment.accessManager; + + GuardiansDeployment memory guardiansDeployment = + new DeployGuardians().run(AccessManager(puffETHDeployment.accessManager), guardians, threshold); + + address pufferOracle = + new DeployPufferOracle().run(puffETHDeployment.accessManager, guardiansDeployment.guardianModule); + + // 2. Upgrade the vault + new UpgradePuffETH().run(puffETHDeployment.pufferVault, puffETHDeployment.accessManager, pufferOracle); + + PufferProtocolDeployment memory pufferDeployment = new DeployPuffer().run( + guardiansDeployment, puffETHDeployment.pufferVault, puffETHDeployment.weth, pufferOracle + ); + + pufferDeployment.pufferDepositor = puffETHDeployment.pufferDepositor; + pufferDeployment.pufferVault = puffETHDeployment.pufferVault; + pufferDeployment.stETH = puffETHDeployment.stETH; + pufferDeployment.weth = puffETHDeployment.weth; if (!isAnvil()) { DAO = _broadcaster; diff --git a/script/DeployGuardians.s.sol b/script/DeployGuardians.s.sol index 51cb9a15..f9647844 100644 --- a/script/DeployGuardians.s.sol +++ b/script/DeployGuardians.s.sol @@ -10,26 +10,21 @@ import { GuardiansDeployment } from "./DeploymentStructs.sol"; // forge script script/1_DeployGuardians.s.sol:DeployGuardians --rpc-url=$EPHEMERY_RPC_URL --sig 'run(address[] calldata, uint256)' "[0xDDDeAfB492752FC64220ddB3E7C9f1d5CcCdFdF0]" 1 contract DeployGuardians is BaseScript { - function run(address[] calldata guardians, uint256 threshold) + function run(AccessManager accessManager, address[] calldata guardians, uint256 threshold) public broadcast returns (GuardiansDeployment memory) { - // Broadcaster is the deployer - AccessManager accessManager = new AccessManager(_broadcaster); vm.label(address(accessManager), "AccessManager"); EnclaveVerifier verifier = new EnclaveVerifier(100, address(accessManager)); GuardianModule module = new GuardianModule(verifier, guardians, threshold, address(accessManager)); - address DAO = payable(vm.envOr("DAO", makeAddr("DAO"))); - string memory obj = ""; vm.serializeAddress(obj, "accessManager", address(accessManager)); vm.serializeAddress(obj, "guardianModule", address(module)); vm.serializeAddress(obj, "enclaveVerifier", address(verifier)); - vm.serializeAddress(obj, "pauser", DAO); string memory finalJson = vm.serializeString(obj, "", ""); @@ -39,7 +34,6 @@ contract DeployGuardians is BaseScript { deployment.accessManager = address(accessManager); deployment.guardianModule = address(module); deployment.enclaveVerifier = address(verifier); - deployment.pauser = DAO; return deployment; } diff --git a/script/DeployPuffer.s.sol b/script/DeployPuffer.s.sol index 71419130..313c7ef0 100644 --- a/script/DeployPuffer.s.sol +++ b/script/DeployPuffer.s.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { PufferPool } from "puffer/PufferPool.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModuleFactory } from "puffer/PufferModuleFactory.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; -import { WithdrawalPool } from "puffer/WithdrawalPool.sol"; -import { NoImplementation } from "puffer/NoImplementation.sol"; +import { NoImplementation } from "pufETH/NoImplementation.sol"; import { PufferModule } from "puffer/PufferModule.sol"; import { NoRestakingModule } from "puffer/NoRestakingModule.sol"; import { ERC1967Proxy } from "openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; @@ -16,16 +14,20 @@ import { EigenPodManagerMock } from "../test/mocks/EigenPodManagerMock.sol"; import { DelegationManagerMock } from "../test/mocks/DelegationManagerMock.sol"; import { BeaconMock } from "../test/mocks/BeaconMock.sol"; import { IDelayedWithdrawalRouter } from "eigenlayer/interfaces/IDelayedWithdrawalRouter.sol"; -import { IEigenPodManager } from "eigenlayer/interfaces/IEigenPodManager.sol"; import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; -import { GuardiansDeployment, PufferDeployment } from "./DeploymentStructs.sol"; +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { GuardiansDeployment, PufferProtocolDeployment } from "./DeploymentStructs.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; +import { IWETH } from "pufETH/interface/Other/IWETH.sol"; /** * @title DeployPuffer * @author Puffer Finance - * @notice Deploys PufferPool Contracts + * @notice Deploys PufferProtocol Contracts * @dev * * @@ -41,45 +43,54 @@ contract DeployPuffer is BaseScript { PufferProtocol pufferProtocolImpl; AccessManager accessManager; ERC1967Proxy proxy; + ERC1967Proxy validatorTicketProxy; PufferProtocol pufferProtocol; - PufferPool pool; - WithdrawalPool withdrawalPool; UpgradeableBeacon beacon; PufferModuleFactory moduleFactory; - address payable treasury; - address eigenPodManager; address delayedWithdrawalRouter; address delegationManager; - function run(GuardiansDeployment calldata guardiansDeployment) public broadcast returns (PufferDeployment memory) { + function run(GuardiansDeployment calldata guardiansDeployment, address pufferVault, address weth, address oracle) + public + broadcast + returns (PufferProtocolDeployment memory) + { string memory obj = ""; accessManager = AccessManager(guardiansDeployment.accessManager); - bytes32 poolSalt = bytes32("pufferPool"); - bytes32 withdrawalPoolSalt = bytes32("withdrawalPool"); if (isMainnet()) { // Mainnet / Mainnet fork - treasury = payable(vm.envAddress("TREASURY")); eigenPodManager = vm.envAddress("EIGENPOD_MANAGER"); delayedWithdrawalRouter = vm.envAddress("DELAYED_WITHDRAWAL_ROUTER"); delegationManager = vm.envAddress("DELEGATION_MANAGER"); } else if (isAnvil()) { // Local chain / tests - treasury = payable(address(1337)); eigenPodManager = address(new EigenPodManagerMock()); delayedWithdrawalRouter = address(0); delegationManager = address(new DelegationManagerMock()); } else { // Testnets - treasury = payable(vm.envOr("TREASURY", address(1337))); eigenPodManager = vm.envOr("EIGENPOD_MANAGER", address(new EigenPodManagerMock())); delayedWithdrawalRouter = vm.envOr("DELAYED_WITHDRAWAL_ROUTER", address(0)); delegationManager = vm.envOr("DELEGATION_MANAGER", address(new DelegationManagerMock())); } + validatorTicketProxy = new ERC1967Proxy(address(new NoImplementation()), ""); + ValidatorTicket validatorTicketImplementation = new ValidatorTicket( + payable(guardiansDeployment.guardianModule), payable(pufferVault), IPufferOracle(oracle) + ); + + NoImplementation(payable(address(validatorTicketProxy))).upgradeToAndCall( + address(validatorTicketImplementation), + abi.encodeCall( + ValidatorTicket.initialize, + (address(accessManager), 5 * FixedPointMathLib.WAD, 5 * 1e17) //@todo recheck 5% treasury, 0.5% guardians + ) + ); + // UUPS proxy for PufferProtocol proxy = new ERC1967Proxy(address(new NoImplementation()), ""); { @@ -94,17 +105,6 @@ contract DeployPuffer is BaseScript { beacon = new UpgradeableBeacon(address(moduleImplementation), address(accessManager)); vm.serializeAddress(obj, "moduleBeacon", address(beacon)); - // Predict Pool address - address predictedPool = computeCreate2Address( - poolSalt, hashInitCode(type(PufferPool).creationCode, abi.encode(proxy, address(accessManager))) - ); - - // Predict Withdrawal pool address - address predictedWithdrawalPool = computeCreate2Address( - withdrawalPoolSalt, - hashInitCode(type(WithdrawalPool).creationCode, abi.encode(predictedPool, address(accessManager))) - ); - moduleFactory = new PufferModuleFactory({ beacon: address(beacon), pufferProtocol: address(proxy), @@ -113,54 +113,29 @@ contract DeployPuffer is BaseScript { // Puffer Service implementation pufferProtocolImpl = new PufferProtocol({ - withdrawalPool: WithdrawalPool(payable(predictedWithdrawalPool)), - pool: PufferPool(payable(predictedPool)), + pufferVault: PufferVaultMainnet(payable(pufferVault)), + validatorTicket: ValidatorTicket(address(validatorTicketProxy)), guardianModule: GuardianModule(payable(guardiansDeployment.guardianModule)), - treasury: treasury, - moduleFactory: address(moduleFactory) + moduleFactory: address(moduleFactory), + oracle: IPufferOracle(oracle) }); } pufferProtocol = PufferProtocol(payable(address(proxy))); - // Deploy pool - pool = new PufferPool{ salt: poolSalt }(pufferProtocol, address(accessManager)); - - withdrawalPool = new WithdrawalPool{ salt: withdrawalPoolSalt }(pool, address(accessManager)); NoRestakingModule noRestaking = new NoRestakingModule(address(accessManager), pufferProtocol, getStakingContract(), bytes32("NO_RESTAKING")); - uint256[] memory smoothingCommitments = new uint256[](14); - - smoothingCommitments[0] = 0.11995984289445429 ether; - smoothingCommitments[1] = 0.11989208274022745 ether; - smoothingCommitments[2] = 0.1197154447609346 ether; - smoothingCommitments[3] = 0.11928478246786729 ether; - smoothingCommitments[4] = 0.11838635147178002 ether; - smoothingCommitments[5] = 0.11699999999999999 ether; - smoothingCommitments[6] = 0.11561364852821997 ether; - smoothingCommitments[7] = 0.11471521753213271 ether; - smoothingCommitments[8] = 0.1142845552390654 ether; - smoothingCommitments[9] = 0.11410791725977254 ether; - smoothingCommitments[10] = 0.1140401571055457 ether; - smoothingCommitments[11] = 0.11401483573893981 ether; - smoothingCommitments[12] = 0.1140054663071664 ether; - smoothingCommitments[13] = 0.1140020121007828 ether; - NoImplementation(payable(address(proxy))).upgradeToAndCall(address(pufferProtocolImpl), ""); // Initialize the Pool - pufferProtocol.initialize({ - accessManager: address(accessManager), - noRestakingModule: address(noRestaking), - smoothingCommitments: smoothingCommitments - }); + pufferProtocol.initialize({ accessManager: address(accessManager), noRestakingModule: address(noRestaking) }); vm.label(address(accessManager), "AccessManager"); + vm.label(address(validatorTicketProxy), "ValidatorTicketProxy"); + vm.label(address(validatorTicketImplementation), "ValidatorTicketImplementation"); vm.label(address(proxy), "PufferProtocolProxy"); vm.label(address(pufferProtocolImpl), "PufferProtocolImplementation"); - vm.label(address(pool), "PufferPool"); - vm.label(address(withdrawalPool), "WithdrawalPool"); vm.label(address(moduleFactory), "PufferModuleFactory"); vm.label(address(beacon), "PufferModuleBeacon"); vm.label(address(guardiansDeployment.enclaveVerifier), "EnclaveVerifier"); @@ -168,8 +143,6 @@ contract DeployPuffer is BaseScript { vm.serializeAddress(obj, "PufferProtocolImplementation", address(pufferProtocolImpl)); vm.serializeAddress(obj, "noRestakingModule", address(noRestaking)); - vm.serializeAddress(obj, "pufferPool", address(pool)); - vm.serializeAddress(obj, "withdrawalPool", address(withdrawalPool)); vm.serializeAddress(obj, "PufferProtocol", address(proxy)); vm.serializeAddress(obj, "moduleFactory", address(moduleFactory)); vm.serializeAddress(obj, "guardianModule", guardiansDeployment.guardianModule); @@ -179,19 +152,23 @@ contract DeployPuffer is BaseScript { vm.writeJson(finalJson, "./output/puffer.json"); // return (pufferProtocol, pool, accessManager); - return PufferDeployment({ + return PufferProtocolDeployment({ + validatorTicket: address(validatorTicketProxy), pufferProtocolImplementation: address(pufferProtocolImpl), NoRestakingModule: address(noRestaking), - pufferPool: address(pool), - withdrawalPool: address(withdrawalPool), pufferProtocol: address(proxy), guardianModule: guardiansDeployment.guardianModule, accessManager: guardiansDeployment.accessManager, enclaveVerifier: guardiansDeployment.enclaveVerifier, pauser: guardiansDeployment.pauser, beacon: address(beacon), - moduleFactory: address(moduleFactory) - }); + moduleFactory: address(moduleFactory), + pufferOracle: address(oracle), + stETH: address(0), // overwritten in DeployEverything + pufferVault: address(0), // overwritten in DeployEverything + pufferDepositor: address(0), // overwritten in DeployEverything + weth: address(0) // overwritten in DeployEverything + }); } function getStakingContract() internal returns (address) { diff --git a/script/DeployPufferOracle.s.sol b/script/DeployPufferOracle.s.sol new file mode 100644 index 00000000..6423e338 --- /dev/null +++ b/script/DeployPufferOracle.s.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { BaseScript } from "script/BaseScript.s.sol"; +import { GuardianModule } from "../src/GuardianModule.sol"; +import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +import { PufferOracle } from "puffer/PufferOracle.sol"; + +// forge script script/DeployPufferOracle.s.sol:DeployPufferOracle --rpc-url=$EPHEMERY_RPC_URL --sig 'run(address[] calldata, uint256)' "[0xDDDeAfB492752FC64220ddB3E7C9f1d5CcCdFdF0]" 1 +contract DeployPufferOracle is BaseScript { + function run(address accessManager, address guardianModule) public broadcast returns (address) { + PufferOracle oracle = new PufferOracle(GuardianModule(payable(guardianModule)), accessManager); + + return address(oracle); + } +} diff --git a/script/DeploymentStructs.sol b/script/DeploymentStructs.sol index 8975d1b5..ea5f5038 100644 --- a/script/DeploymentStructs.sol +++ b/script/DeploymentStructs.sol @@ -12,13 +12,11 @@ struct GuardiansDeployment { } /** - * @notice PufferDeployment + * @notice PufferProtocolDeployment */ -struct PufferDeployment { +struct PufferProtocolDeployment { address pufferProtocolImplementation; address NoRestakingModule; - address pufferPool; - address withdrawalPool; address pufferProtocol; address guardianModule; address accessManager; @@ -26,4 +24,10 @@ struct PufferDeployment { address pauser; address beacon; // Beacon for Puffer modules address moduleFactory; + address validatorTicket; + address pufferOracle; + address pufferDepositor; // from pufETH repository (dependency) + address pufferVault; // from pufETH repository (dependency) + address stETH; // from pufETH repository (dependency) + address weth; // from pufETH repository (dependency) } diff --git a/script/SetupAccess.s.sol b/script/SetupAccess.s.sol index 12dc914e..6fc0e317 100644 --- a/script/SetupAccess.s.sol +++ b/script/SetupAccess.s.sol @@ -4,16 +4,20 @@ pragma solidity >=0.8.0 <0.9.0; import { BaseScript } from "script/BaseScript.s.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; import { PufferModuleFactory } from "puffer/PufferModuleFactory.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; import { EnclaveVerifier } from "puffer/EnclaveVerifier.sol"; -import { PufferDeployment } from "./DeploymentStructs.sol"; +import { PufferOracle } from "puffer/PufferOracle.sol"; +import { PufferProtocolDeployment } from "./DeploymentStructs.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import { NoRestakingModule } from "puffer/NoRestakingModule.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; +import { UUPSUpgradeable } from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { GenerateAccessManagerCallData } from "pufETHScript/GenerateAccessManagerCallData.sol"; +import { ROLE_ID_OPERATIONS, ROLE_ID_PUFFER_PROTOCOL } from "pufETHScript/Roles.sol"; -uint64 constant ROLE_ID_PUFFER_PROTOCOL = 1; uint64 constant ROLE_ID_DAO = 77; uint64 constant ROLE_ID_GUARDIANS = 88; uint64 constant ROLE_ID_PAUSER = 999; @@ -21,96 +25,123 @@ uint64 constant ROLE_ID_PAUSER = 999; contract SetupAccess is BaseScript { AccessManager internal accessManager; - PufferDeployment internal pufferDeployment; + PufferProtocolDeployment internal pufferDeployment; - function run(PufferDeployment memory deployment, address DAO) external broadcast { + function run(PufferProtocolDeployment memory deployment, address DAO) external broadcast { pufferDeployment = deployment; accessManager = AccessManager(payable(deployment.accessManager)); // We do one multicall to setup everything bytes[] memory rolesCalldatas = _grantRoles(DAO); bytes[] memory pufferProtocolRoles = _setupPufferProtocolRoles(); - bytes[] memory pufferPoolRoles = _setupPufferPoolRoles(); bytes[] memory noRestakingModuleRoles = _setupNoRestakingModuleRoles(); + bytes[] memory validatorTicketRoles = _setupValidatorTicketsAccess(); + bytes[] memory vaultMainnetAccess = _setupPufferVaultMainnetAccess(); + bytes[] memory pufferOracleAccess = _setupPufferOracleAccess(); - bytes[] memory calldatas = new bytes[](15); + bytes[] memory calldatas = new bytes[](18); calldatas[0] = _setupGuardianModuleRoles(); calldatas[1] = _setupEnclaveVerifierRoles(); - calldatas[2] = _setupWithdrawalPoolRoles(); - calldatas[3] = _setupUpgradeableBeacon(); - calldatas[4] = rolesCalldatas[0]; - calldatas[5] = rolesCalldatas[1]; - calldatas[6] = rolesCalldatas[2]; + calldatas[2] = _setupUpgradeableBeacon(); + calldatas[3] = rolesCalldatas[0]; + calldatas[4] = rolesCalldatas[1]; + calldatas[5] = rolesCalldatas[2]; - calldatas[7] = pufferProtocolRoles[0]; - calldatas[8] = pufferProtocolRoles[1]; - calldatas[9] = pufferProtocolRoles[2]; + calldatas[6] = pufferProtocolRoles[0]; + calldatas[7] = pufferProtocolRoles[1]; + calldatas[8] = pufferProtocolRoles[2]; - calldatas[10] = pufferPoolRoles[0]; - calldatas[11] = pufferPoolRoles[1]; + calldatas[9] = noRestakingModuleRoles[0]; + calldatas[10] = noRestakingModuleRoles[1]; + calldatas[11] = noRestakingModuleRoles[2]; - calldatas[12] = noRestakingModuleRoles[0]; - calldatas[13] = noRestakingModuleRoles[1]; - calldatas[14] = noRestakingModuleRoles[2]; + calldatas[12] = validatorTicketRoles[0]; + calldatas[13] = validatorTicketRoles[1]; - // calldatas[16] = _setupPauser(); + calldatas[14] = vaultMainnetAccess[0]; + calldatas[15] = vaultMainnetAccess[1]; + + calldatas[16] = pufferOracleAccess[0]; + calldatas[17] = pufferOracleAccess[1]; accessManager.multicall(calldatas); + + // This will be executed by the operations multisig on mainnet + bytes memory cd = new GenerateAccessManagerCallData().run(deployment.pufferVault, deployment.pufferProtocol); + (bool s,) = address(accessManager).call(cd); + require(s, "failed setupAccess GenerateAccessManagerCallData"); } - function _setupPauser() internal view returns (bytes memory) { - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = AccessManager.setTargetClosed.selector; + function _setupPufferOracleAccess() internal view returns (bytes[] memory) { + bytes[] memory calldatas = new bytes[](2); - return abi.encodeWithSelector( - AccessManager.setTargetFunctionRole.selector, address(accessManager), selectors, ROLE_ID_PAUSER + // Only for PufferProtocol + bytes4[] memory protocolSelectors = new bytes4[](1); + protocolSelectors[0] = PufferOracle.provisionNode.selector; + + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + pufferDeployment.pufferOracle, + protocolSelectors, + ROLE_ID_PUFFER_PROTOCOL ); + + // DAO selectors + bytes4[] memory daoSelectors = new bytes4[](1); + daoSelectors[0] = PufferOracle.setMintPrice.selector; + + calldatas[1] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, pufferDeployment.pufferOracle, daoSelectors, ROLE_ID_DAO + ); + + return calldatas; } - function _setupWithdrawalPoolRoles() internal view returns (bytes memory) { - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = bytes4(hex"4782f779"); // IWithdrawalPool.withdrawETH.selector; - selectors[1] = bytes4(hex"945fca09"); // IWithdrawalPool.withdrawETH Permit version + function _setupPufferVaultMainnetAccess() internal view returns (bytes[] memory) { + bytes[] memory calldatas = new bytes[](2); - return abi.encodeWithSelector( + bytes4[] memory publicSelectors = new bytes4[](1); + publicSelectors[0] = PufferVaultMainnet.burn.selector; + + calldatas[0] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, - pufferDeployment.withdrawalPool, - selectors, + pufferDeployment.pufferVault, + publicSelectors, accessManager.PUBLIC_ROLE() ); - } - function _setupGuardianModuleRoles() internal view returns (bytes memory) { - bytes4[] memory selectors = new bytes4[](4); - selectors[0] = GuardianModule.setGuardianEnclaveMeasurements.selector; - selectors[1] = GuardianModule.addGuardian.selector; - selectors[2] = GuardianModule.removeGuardian.selector; - selectors[3] = GuardianModule.changeThreshold.selector; + bytes4[] memory daoSelectors = new bytes4[](1); + daoSelectors[0] = PufferVaultMainnet.setDailyWithdrawalLimit.selector; - return abi.encodeWithSelector( - AccessManager.setTargetFunctionRole.selector, pufferDeployment.guardianModule, selectors, ROLE_ID_DAO + calldatas[1] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + pufferDeployment.pufferVault, + daoSelectors, + ROLE_ID_OPERATIONS //@todo? ); + + return calldatas; } - function _setupPufferPoolRoles() internal view returns (bytes[] memory) { + function _setupValidatorTicketsAccess() internal view returns (bytes[] memory) { bytes[] memory calldatas = new bytes[](2); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = PufferPool.transferETH.selector; + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = ValidatorTicket.setProtocolFeeRate.selector; + selectors[1] = UUPSUpgradeable.upgradeToAndCall.selector; + selectors[2] = ValidatorTicket.setGuardiansFeeRate.selector; calldatas[0] = abi.encodeWithSelector( - AccessManager.setTargetFunctionRole.selector, - pufferDeployment.pufferPool, - selectors, - ROLE_ID_PUFFER_PROTOCOL + AccessManager.setTargetFunctionRole.selector, pufferDeployment.validatorTicket, selectors, ROLE_ID_DAO ); - bytes4[] memory publicSelectors = new bytes4[](1); - publicSelectors[0] = PufferPool.depositETH.selector; + bytes4[] memory publicSelectors = new bytes4[](2); + publicSelectors[0] = ValidatorTicket.purchaseValidatorTicket.selector; + publicSelectors[1] = ValidatorTicket.burn.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, - pufferDeployment.pufferPool, + pufferDeployment.validatorTicket, publicSelectors, accessManager.PUBLIC_ROLE() ); @@ -118,6 +149,18 @@ contract SetupAccess is BaseScript { return calldatas; } + function _setupGuardianModuleRoles() internal view returns (bytes memory) { + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = GuardianModule.setGuardianEnclaveMeasurements.selector; + selectors[1] = GuardianModule.addGuardian.selector; + selectors[2] = GuardianModule.removeGuardian.selector; + selectors[3] = GuardianModule.changeThreshold.selector; + + return abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, pufferDeployment.guardianModule, selectors, ROLE_ID_DAO + ); + } + function _setupUpgradeableBeacon() internal view returns (bytes memory) { bytes4[] memory selectors = new bytes4[](1); selectors[0] = UpgradeableBeacon.upgradeTo.selector; @@ -145,7 +188,7 @@ contract SetupAccess is BaseScript { ); bytes4[] memory selectorsForGuardians = new bytes4[](1); - selectorsForGuardians[0] = bytes4(hex"abfaad62"); // signature for `function postRewardsRoot(bytes32 root, uint256 blockNumber)` + selectorsForGuardians[0] = NoRestakingModule.postRewardsRoot.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, @@ -155,7 +198,7 @@ contract SetupAccess is BaseScript { ); bytes4[] memory publicSelectors = new bytes4[](1); - publicSelectors[0] = bytes4(hex"6f06f422"); // collectRewards + publicSelectors[0] = NoRestakingModule.collectRewards.selector; calldatas[2] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, @@ -179,17 +222,13 @@ contract SetupAccess is BaseScript { function _setupPufferProtocolRoles() internal view returns (bytes[] memory) { bytes[] memory calldatas = new bytes[](3); - bytes4[] memory selectors = new bytes4[](10); - selectors[0] = PufferProtocol.setProtocolFeeRate.selector; - selectors[1] = PufferProtocol.setSmoothingCommitments.selector; - selectors[2] = PufferProtocol.createPufferModule.selector; - selectors[3] = PufferProtocol.setModuleWeights.selector; - selectors[4] = PufferProtocol.setValidatorLimitPerInterval.selector; - selectors[5] = PufferProtocol.changeModule.selector; - selectors[6] = bytes4(hex"4f1ef286"); // signature for UUPS.upgradeToAndCall(address newImplementation, bytes memory data) - selectors[7] = PufferProtocol.setGuardiansFeeRate.selector; - selectors[8] = PufferProtocol.setWithdrawalPoolRate.selector; - selectors[9] = PufferProtocol.setValidatorLimitPerModule.selector; + bytes4[] memory selectors = new bytes4[](6); + selectors[0] = PufferProtocol.createPufferModule.selector; + selectors[1] = PufferProtocol.setModuleWeights.selector; + selectors[2] = PufferProtocol.changeModule.selector; + selectors[3] = UUPSUpgradeable.upgradeToAndCall.selector; + selectors[4] = PufferProtocol.setValidatorLimitPerModule.selector; + selectors[5] = PufferProtocol.changeMinimumVTAmount.selector; calldatas[0] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, @@ -198,11 +237,10 @@ contract SetupAccess is BaseScript { ROLE_ID_DAO ); - bytes4[] memory guardianSelectors = new bytes4[](4); + bytes4[] memory guardianSelectors = new bytes4[](3); guardianSelectors[0] = PufferProtocol.skipProvisioning.selector; - guardianSelectors[1] = PufferProtocol.stopValidator.selector; - guardianSelectors[2] = PufferProtocol.proofOfReserve.selector; - guardianSelectors[3] = PufferProtocol.postFullWithdrawalsRoot.selector; + guardianSelectors[1] = PufferProtocol.retrieveBond.selector; + guardianSelectors[2] = PufferProtocol.postFullWithdrawalsRoot.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, @@ -211,9 +249,10 @@ contract SetupAccess is BaseScript { ROLE_ID_GUARDIANS ); - bytes4[] memory publicSelectors = new bytes4[](2); + bytes4[] memory publicSelectors = new bytes4[](3); publicSelectors[0] = PufferProtocol.registerValidatorKey.selector; - publicSelectors[1] = PufferProtocol.registerValidatorKeyPermit.selector; + publicSelectors[1] = PufferProtocol.depositValidatorTickets.selector; + publicSelectors[2] = PufferProtocol.withdrawValidatorTickets.selector; calldatas[2] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, diff --git a/script/UpgradeProtocol.s.sol b/script/UpgradeProtocol.s.sol deleted file mode 100644 index ad2eaa8b..00000000 --- a/script/UpgradeProtocol.s.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { BaseScript } from "script/BaseScript.s.sol"; -import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; -import { GuardianModule } from "puffer/GuardianModule.sol"; -import { WithdrawalPool } from "puffer/WithdrawalPool.sol"; - -/** - * @title Deposit ETH script - * @author Puffer Finance - * @notice Calls the `depositETH` function on PufferPool - * @dev Example on how to run the script - * forge script script/UpgradeProtocol.s.sol:UpgradeProtocol --rpc-url=$HOLESKY_RPC_URL --broadcast - */ -contract UpgradeProtocol is BaseScript { - function run() external broadcast { - address payable protocolProxy = payable(0x4982C744Ef2694Af2970D3eB8a58744ed3cB1b1D); - - PufferProtocol newImplementation = new PufferProtocol({ - withdrawalPool: WithdrawalPool(payable(0x378b738c0Cd4e5B373f943b1c9951730E5a29E5b)), - pool: PufferPool(payable(0x90Daec4Cee7e7A4E5499e9E864a1eb89Bb19b8Ed)), - guardianModule: GuardianModule(payable(0x66eb09811E1e46D60eD1421884E9FD76cbE555cA)), - treasury: payable(0x61A44645326846F9b5d9c6f91AD27C3aD28EA390), - moduleFactory: 0x05B9c7bc894DDB37BC6Cc42EE1D2de45782aeA80 - }); - - PufferProtocol(protocolProxy).upgradeToAndCall(address(newImplementation), ""); - } -} diff --git a/src/EnclaveVerifier.sol b/src/EnclaveVerifier.sol index f5b80226..4a582dcc 100644 --- a/src/EnclaveVerifier.sol +++ b/src/EnclaveVerifier.sol @@ -12,7 +12,6 @@ import { AccessManaged } from "openzeppelin/access/manager/AccessManaged.sol"; * @author Puffer Finance * @custom:security-contact security@puffer.fi */ - contract EnclaveVerifier is IEnclaveVerifier, AccessManaged, RAVE { /** * @dev RSA Public key for Intel: https://api.portal.trustedservices.intel.com/documentation diff --git a/src/GuardianModule.sol b/src/GuardianModule.sol index 34efb86b..9d20381f 100644 --- a/src/GuardianModule.sol +++ b/src/GuardianModule.sol @@ -155,20 +155,18 @@ contract GuardianModule is AccessManaged, IGuardianModule { * @inheritdoc IGuardianModule */ function validateProofOfReserve( - uint256 ethAmount, uint256 lockedETH, - uint256 pufETHTotalSupply, uint256 blockNumber, - uint256 numberOfActiveValidators, + uint256 numberOfActivePufferValidators, + uint256 totalNumberOfValidators, bytes[] calldata guardianSignatures ) external view { // Recreate the message hash bytes32 signedMessageHash = LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: ethAmount, lockedETH: lockedETH, - pufETHTotalSupply: pufETHTotalSupply, blockNumber: blockNumber, - numberOfActiveValidators: numberOfActiveValidators + totalNumberOfValidators: totalNumberOfValidators, + numberOfActivePufferValidators: numberOfActivePufferValidators }); // Check the signatures @@ -184,6 +182,8 @@ contract GuardianModule is AccessManaged, IGuardianModule { * @inheritdoc IGuardianModule */ function validateProvisionNode( + uint256 validatorIndex, + uint256 vtBurnOffset, bytes memory pubKey, bytes calldata signature, bytes calldata withdrawalCredentials, @@ -191,9 +191,14 @@ contract GuardianModule is AccessManaged, IGuardianModule { bytes[] calldata guardianEnclaveSignatures ) external view { // Recreate the message hash - bytes32 signedMessageHash = LibGuardianMessages._getBeaconDepositMessageToBeSigned( - pubKey, signature, withdrawalCredentials, depositDataRoot - ); + bytes32 signedMessageHash = LibGuardianMessages._getBeaconDepositMessageToBeSigned({ + validatorIndex: validatorIndex, + vtBurnOffset: vtBurnOffset, + pubKey: pubKey, + signature: signature, + withdrawalCredentials: withdrawalCredentials, + depositDataRoot: depositDataRoot + }); // Check the signatures bool validSignatures = validateGuardiansEnclaveSignatures({ diff --git a/src/LibGuardianMessages.sol b/src/LibGuardianMessages.sol index 8e27e366..fa8703ab 100644 --- a/src/LibGuardianMessages.sol +++ b/src/LibGuardianMessages.sol @@ -13,18 +13,24 @@ library LibGuardianMessages { /** * @notice Returns the message that the guardian's enclave needs to sign + * @param validatorIndex is the validator index in Puffer + * @param vtBurnOffset is an offset used such that VTs only burn after the validator is active * @param signature is the BLS signature of the deposit data * @param withdrawalCredentials are the withdrawal credentials for this validator * @param depositDataRoot is the hash of the deposit data * @return hash of the data */ function _getBeaconDepositMessageToBeSigned( + uint256 validatorIndex, + uint256 vtBurnOffset, bytes memory pubKey, bytes memory signature, bytes memory withdrawalCredentials, bytes32 depositDataRoot ) internal pure returns (bytes32) { - return keccak256(abi.encode(pubKey, withdrawalCredentials, signature, depositDataRoot)).toEthSignedMessageHash(); + //solhint-disable-next-line func-named-parameters + return keccak256(abi.encode(validatorIndex, pubKey, withdrawalCredentials, signature, depositDataRoot)) + .toEthSignedMessageHash(); } /** @@ -55,23 +61,21 @@ library LibGuardianMessages { /** * @notice Returns the message to be signed for the proof of reserve - * @param ethAmount is the amount of ETH in the reserve * @param lockedETH is the amount of locked ETH in the reserve - * @param pufETHTotalSupply is the total supply of pufETH tokens * @param blockNumber is the block number of the proof of reserve - * @param numberOfActiveValidators is the number of all active validators on Beacon Chain + * @param numberOfActivePufferValidators is the number of active Puffer Validators + * @param totalNumberOfValidators is the number of total Validators * @return the message to be signed */ function _getProofOfReserveMessage( - uint256 ethAmount, uint256 lockedETH, - uint256 pufETHTotalSupply, uint256 blockNumber, - uint256 numberOfActiveValidators + uint256 numberOfActivePufferValidators, + uint256 totalNumberOfValidators ) internal pure returns (bytes32) { // All guardians use the same nonce //solhint-disable-next-line func-named-parameters - return keccak256(abi.encode(ethAmount, lockedETH, pufETHTotalSupply, blockNumber, numberOfActiveValidators)) + return keccak256(abi.encode(lockedETH, blockNumber, numberOfActivePufferValidators, totalNumberOfValidators)) .toEthSignedMessageHash(); } diff --git a/src/NoImplementation.sol b/src/NoImplementation.sol deleted file mode 100644 index cead12d6..00000000 --- a/src/NoImplementation.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { UUPSUpgradeable } from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -contract NoImplementation is UUPSUpgradeable { - function _authorizeUpgrade(address newImplementation) internal virtual override { - // anybody can steal this proxy - } -} diff --git a/src/NoRestakingModule.sol b/src/NoRestakingModule.sol index e05a5440..c08080f2 100644 --- a/src/NoRestakingModule.sol +++ b/src/NoRestakingModule.sol @@ -35,15 +35,15 @@ contract NoRestakingModule is IPufferModule, AccessManaged, TokenRescuer { /** * @notice Thrown if the rewards are already claimed for a `blockNumber` - * @dev Signature "0x916ba7f3" + * @dev Signature "0xa9214540" */ - error AlreadyClaimed(uint256 blockNumber, bytes32 pubKeyHash); + error AlreadyClaimed(uint256 blockNumber, address node); /** * @notice Thrown if the there is nothing to be claimed for the provided information - * @dev Signature "0xb9eec102" + * @dev Signature "0x64ab3466" */ - error NothingToClaim(bytes32 pubKeyHash); + error NothingToClaim(address node); /** * @notice Emitted when the rewards MerkleRoot `root` for a `blockNumber` is posted @@ -66,9 +66,9 @@ contract NoRestakingModule is IPufferModule, AccessManaged, TokenRescuer { mapping(uint256 blockNumber => bytes32 root) public rewardsRoots; /** - * @notice Mapping that stores which validators have claimed the rewards for a certain blockNumber + * @notice Mapping that stores which node operators have claimed the rewards for a certain blockNumber */ - mapping(uint256 blockNumber => mapping(bytes32 pubKeyHash => bool claimed)) public claimedRewards; + mapping(uint256 blockNumber => mapping(address node => bool claimed)) public claimedRewards; /** * @dev The last block number for when the rewards root was posted @@ -109,41 +109,39 @@ contract NoRestakingModule is IPufferModule, AccessManaged, TokenRescuer { } /** - * @notice Submit a valid MerkleProof and the staking rewards will be sent to node operator - * @dev Anybody can trigger a claim of the rewards for any validator as long as the proofs submitted are valid + * @notice Submit a valid MerkleProof and all their validators' staking rewards will be sent to node operator + * @dev Anybody can trigger a claim of the rewards for any node operator as long as the proofs submitted are valid * - * @param node is a node operator's wallet - * @param pubKeyHash is a keccak256 hash of the validator's public key + * @param node is a node operator's wallet address * @param blockNumbers is the array of block numbers for which the sender is claiming the rewards * @param amounts is the array of amounts to claim * @param merkleProofs is the array of Merkle proofs */ function collectRewards( address node, - bytes32 pubKeyHash, uint256[] calldata blockNumbers, uint256[] calldata amounts, bytes32[][] calldata merkleProofs ) external restricted { - // Anybody can submit a valid proof and the ETH will be sent to the node + // Anybody can submit a valid proof and the ETH will be sent to the node operator uint256 ethToSend = 0; for (uint256 i = 0; i < amounts.length; ++i) { - if (claimedRewards[blockNumbers[i]][pubKeyHash]) { - revert AlreadyClaimed(blockNumbers[i], pubKeyHash); + if (claimedRewards[blockNumbers[i]][node]) { + revert AlreadyClaimed(blockNumbers[i], node); } bytes32 rewardsRoot = rewardsRoots[blockNumbers[i]]; - bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(node, pubKeyHash, amounts[i])))); + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(node, amounts[i])))); if (MerkleProof.verifyCalldata(merkleProofs[i], rewardsRoot, leaf)) { - claimedRewards[blockNumbers[i]][pubKeyHash] = true; + claimedRewards[blockNumbers[i]][node] = true; ethToSend += amounts[i]; } } if (ethToSend == 0) { - revert NothingToClaim(pubKeyHash); + revert NothingToClaim(node); } node.safeTransferETH(ethToSend); diff --git a/src/PufferModule.sol b/src/PufferModule.sol index d730a2ff..1cd6c449 100644 --- a/src/PufferModule.sol +++ b/src/PufferModule.sol @@ -35,9 +35,9 @@ contract PufferModule is IPufferModule, Initializable, AccessManagedUpgradeable /** * @notice Thrown if the rewards are already claimed for a `blockNumber` - * @dev Signature "0x916ba7f3" + * @dev Signature "0xa9214540" */ - error AlreadyClaimed(uint256 blockNumber, bytes32 pubKeyHash); + error AlreadyClaimed(uint256 blockNumber, address node); /** * @notice Thrown if guardians try to post root for an invalid block number @@ -47,9 +47,9 @@ contract PufferModule is IPufferModule, Initializable, AccessManagedUpgradeable /** * @notice Thrown if the there is nothing to be claimed for the provided information - * @dev Signature "0xb9eec102" + * @dev Signature "0x64ab3466" */ - error NothingToClaim(bytes32 pubKeyHash); + error NothingToClaim(address node); /** * @notice Emitted when the rewards MerkleRoot `root` for a `blockNumber` is posted @@ -114,7 +114,7 @@ contract PufferModule is IPufferModule, Initializable, AccessManagedUpgradeable /** * @notice Mapping that stores which validators have claimed the rewards for a certain blockNumber */ - mapping(uint256 blockNumber => mapping(bytes32 pubKeyHash => bool claimed)) claimedRewards; + mapping(uint256 blockNumber => mapping(address node => bool claimed)) claimedRewards; } constructor( @@ -206,43 +206,41 @@ contract PufferModule is IPufferModule, Initializable, AccessManagedUpgradeable } /** - * @notice Submit a valid MerkleProof and the staking rewards will be sent to node operator - * @dev Anybody can trigger a claim of the rewards for any validator as long as the proofs submitted are valid + * @notice Submit a valid MerkleProof all their validators' staking rewards will be sent to node operator + * @dev Anybody can trigger a claim of the rewards for any node operator as long as the proofs submitted are valid * - * @param node is a node operator's wallet - * @param pubKeyHash is a keccak256 hash of the validator's public key + * @param node is a node operator's wallet address * @param blockNumbers is the array of block numbers for which the sender is claiming the rewards * @param amounts is the array of amounts to claim * @param merkleProofs is the array of Merkle proofs */ function collectRewards( address node, - bytes32 pubKeyHash, uint256[] calldata blockNumbers, uint256[] calldata amounts, bytes32[][] calldata merkleProofs ) external { PufferModuleStorage storage $ = _getPufferProtocolStorage(); - // Anybody can submit a valid proof and the ETH will be sent to the node + // Anybody can submit a valid proof and the ETH will be sent to the node operator uint256 ethToSend = 0; for (uint256 i = 0; i < amounts.length; ++i) { - if ($.claimedRewards[blockNumbers[i]][pubKeyHash]) { - revert AlreadyClaimed(blockNumbers[i], pubKeyHash); + if ($.claimedRewards[blockNumbers[i]][node]) { + revert AlreadyClaimed(blockNumbers[i], node); } bytes32 rewardsRoot = $.rewardsRoots[blockNumbers[i]]; - bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(node, pubKeyHash, amounts[i])))); + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(node, amounts[i])))); if (MerkleProof.verifyCalldata(merkleProofs[i], rewardsRoot, leaf)) { - $.claimedRewards[blockNumbers[i]][pubKeyHash] = true; + $.claimedRewards[blockNumbers[i]][node] = true; ethToSend += amounts[i]; } } if (ethToSend == 0) { - revert NothingToClaim(pubKeyHash); + revert NothingToClaim(node); } node.safeTransferETH(ethToSend); diff --git a/src/PufferOracle.sol b/src/PufferOracle.sol new file mode 100644 index 00000000..9aea8e99 --- /dev/null +++ b/src/PufferOracle.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { IGuardianModule } from "puffer/interface/IGuardianModule.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; +import { AccessManaged } from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; + +/** + * @title PufferOracle + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +contract PufferOracle is IPufferOracle, AccessManaged { + /** + * @dev Number of blocks + */ + // @todo + // slither-disable-next-line unused-state + uint256 internal constant _UPDATE_INTERVAL = 1; + + /** + * @dev Burst threshold + */ + uint256 internal constant _BURST_THRESHOLD = 22; + + /** + * @dev Locked ETH amount in Beacon Chain + * Slot 1 + */ + uint256 public lockedETH; + + /** + * @dev Block number for when the values were updated + * Slot 2 + */ + uint256 public lastUpdate; + + /** + * @dev Price in ETH to mint one Validator Ticket + */ + uint256 internal _validatorTicketPrice; + + /** + * @dev Number of active Puffer validators + */ + uint256 internal _numberOfActivePufferValidators; + + /** + * @dev Total number of Validators + */ + uint256 internal _totalNumberOfValidators; + + IGuardianModule public immutable GUARDIAN_MODULE; + + constructor(IGuardianModule guardianModule, address accessManager) AccessManaged(accessManager) { + GUARDIAN_MODULE = guardianModule; + _totalNumberOfValidators = 927122; // Oracle will be updated with the correct value + _setMintPrice(uint56(0.01 ether)); + } + + function proofOfReserve( + uint256 newLockedETH, + uint256 blockNumber, + uint256 numberOfActivePufferValidators, + uint256 totalNumberOfValidators, + bytes[] calldata guardianSignatures + ) external { + GUARDIAN_MODULE.validateProofOfReserve({ + lockedETH: newLockedETH, + blockNumber: blockNumber, + numberOfActivePufferValidators: numberOfActivePufferValidators, + totalNumberOfValidators: totalNumberOfValidators, + guardianSignatures: guardianSignatures + }); + + if ((block.number - lastUpdate) < _UPDATE_INTERVAL) { + revert OutsideUpdateWindow(); + } + + lockedETH = newLockedETH; + lastUpdate = blockNumber; + _numberOfActivePufferValidators = numberOfActivePufferValidators; + _totalNumberOfValidators = totalNumberOfValidators; + + //@todo change the event + emit BackingUpdated(blockNumber, newLockedETH); + } + + /** + * @notice Increases the lockedETH amount by 32 ETH + * @dev Restricted to PufferProtocol contract + */ + function provisionNode() external restricted { + lockedETH += 32 ether; + unchecked { + ++_numberOfActivePufferValidators; + } + } + + /** + * @notice Updates the price to mint VT + * @param newPrice The new price to set for minting VT + * @dev Restricted to the DAO + */ + function setMintPrice(uint56 newPrice) external restricted { + _setMintPrice(newPrice); + } + + function _setMintPrice(uint56 newPrice) internal { + emit ValidatorTicketMintPriceUpdated(_validatorTicketPrice, newPrice); + _validatorTicketPrice = newPrice; + } + + /** + * @inheritdoc IPufferOracle + */ + function getLockedEthAmount() external view returns (uint256) { + return lockedETH; + } + + /** + * @inheritdoc IPufferOracle + */ + function isOverBurstThreshold() external view returns (bool) { + return ((_numberOfActivePufferValidators * 100 / _totalNumberOfValidators) > _BURST_THRESHOLD); + } + + /** + * @inheritdoc IPufferOracle + */ + function getValidatorTicketPrice() external view returns (uint256) { + return _validatorTicketPrice; + } +} diff --git a/src/PufferPool.sol b/src/PufferPool.sol deleted file mode 100644 index cd736131..00000000 --- a/src/PufferPool.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { TokenRescuer } from "puffer/TokenRescuer.sol"; -import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; -import { ERC20Permit } from "openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; -import { IPufferPool } from "puffer/interface/IPufferPool.sol"; -import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { PufferPoolStorage } from "puffer/struct/PufferPoolStorage.sol"; -import { AccessManaged } from "openzeppelin/access/manager/AccessManaged.sol"; - -/** - * @title PufferPool - * @author Puffer Finance - * @custom:security-contact security@puffer.fi - */ -contract PufferPool is IPufferPool, TokenRescuer, ERC20Permit, AccessManaged { - using SafeTransferLib for address; - - constructor(PufferProtocol protocol, address initialAuthority) - payable - TokenRescuer(protocol) - ERC20("Puffer ETH", "pufETH") - ERC20Permit("pufETH") - AccessManaged(initialAuthority) - { } - - receive() external payable { } - - /** - * @inheritdoc IPufferPool - */ - function depositETH() public payable restricted returns (uint256) { - if (msg.value == 0) { - revert InvalidETHAmount(); - } - uint256 pufETHAmount = _calculateETHToPufETHAmount(msg.value); - - emit Deposited(msg.sender, msg.value, pufETHAmount); - - _mint(msg.sender, pufETHAmount); - - return pufETHAmount; - } - - /** - * @inheritdoc IPufferPool - */ - function burn(uint256 pufETHAmount) external { - _burn(msg.sender, pufETHAmount); - } - - function transferETH(address to, uint256 ethAmount) external restricted { - to.safeTransferETH(ethAmount); - } - - /** - * @notice Recovers ERC20 with an exception of pufETH - */ - function recoverERC20(address token) external override { - if (token == address(this)) { - revert InvalidToken(token); - } - token.safeTransferAll(PUFFER_PROTOCOL.TREASURY()); - } - - /** - * @inheritdoc IPufferPool - */ - function calculateETHToPufETHAmount(uint256 amount) public view returns (uint256) { - return _calculateETHToPufETHAmount(amount); - } - - /** - * @inheritdoc IPufferPool - */ - function calculatePufETHtoETHAmount(uint256 pufETHAmount) public view returns (uint256) { - return FixedPointMathLib.mulWad(pufETHAmount, getPufETHtoETHExchangeRate()); - } - - /** - * @inheritdoc IPufferPool - */ - function getPufETHtoETHExchangeRate() public view returns (uint256) { - return _getPufETHtoETHExchangeRate(); - } - - function _getPufETHtoETHExchangeRate() internal view returns (uint256) { - PufferPoolStorage memory data = PUFFER_PROTOCOL.getPufferPoolStorage(); - // slither-disable-next-line incorrect-equality - if (data.pufETHTotalSupply == 0) { - return FixedPointMathLib.WAD; - } - - return FixedPointMathLib.divWad((data.lockedETH + data.ethAmount), data.pufETHTotalSupply); - } - - /** - * @dev Internal function for calculating the ETH to pufETH amount when ETH is being sent in the transaction - */ - function _calculateETHToPufETHAmount(uint256 amount) internal view returns (uint256) { - return FixedPointMathLib.divWad(amount, _getPufETHtoETHExchangeRate()); - } -} diff --git a/src/PufferProtocol.sol b/src/PufferProtocol.sol index 87dc047f..3794d1aa 100644 --- a/src/PufferProtocol.sol +++ b/src/PufferProtocol.sol @@ -5,24 +5,23 @@ import { IPufferProtocol } from "puffer/interface/IPufferProtocol.sol"; import { AccessManagedUpgradeable } from "openzeppelin-upgradeable/access/manager/AccessManagedUpgradeable.sol"; import { UUPSUpgradeable } from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { PufferProtocolStorage } from "puffer/PufferProtocolStorage.sol"; -import { IPufferPool } from "puffer/interface/IPufferPool.sol"; import { IPufferModuleFactory } from "puffer/interface/IPufferModuleFactory.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; import { IGuardianModule } from "puffer/interface/IGuardianModule.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; import { ValidatorKeyData } from "puffer/struct/ValidatorKeyData.sol"; import { Validator } from "puffer/struct/Validator.sol"; import { Permit } from "puffer/struct/Permit.sol"; import { Status } from "puffer/struct/Status.sol"; -import { ProtocolStorage } from "puffer/struct/ProtocolStorage.sol"; -import { PufferPoolStorage } from "puffer/struct/PufferPoolStorage.sol"; +import { ProtocolStorage, NodeInfo } from "puffer/struct/ProtocolStorage.sol"; import { Unauthorized } from "puffer/Errors.sol"; import { LibBeaconchainContract } from "puffer/LibBeaconchainContract.sol"; import { MerkleProof } from "openzeppelin/utils/cryptography/MerkleProof.sol"; import { IERC20Permit } from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol"; -import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { SafeCastLib } from "solady/utils/SafeCastLib.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import { StoppedValidatorInfo } from "puffer/struct/StoppedValidatorInfo.sol"; /** * @title PufferProtocol @@ -30,13 +29,15 @@ import { SafeCastLib } from "solady/utils/SafeCastLib.sol"; * @custom:security-contact security@puffer.fi */ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgradeable, PufferProtocolStorage { - using SafeTransferLib for address; - using SafeTransferLib for address payable; + /** + * @dev Validator ticket loss rate per second + */ + uint256 internal constant _VT_LOSS_RATE_PER_SECOND = 11574074074075; // (1 ether / 1 days) rounded up /** - * @dev Burst threshold + * @dev Validator ticket loss rate per second */ - uint256 internal constant _BURST_THRESHOLD = 22; + uint256 internal constant _VT_LOSS_RATE_PER_SECOND_DOWN = 11574074074074; // (1 ether / 1 days) rounded down /** * @dev BLS public keys are 48 bytes long @@ -59,105 +60,62 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad bytes32 internal constant _NO_RESTAKING = bytes32("NO_RESTAKING"); /** - * @dev Number of blocks - * 7141 * 12(avg block time) = 85692 seconds - * 85692 seconds ~ 23.8 hours - */ - uint256 internal constant _UPDATE_INTERVAL = 7141; - - /** - * @dev Puffer Finance treasury + * @inheritdoc IPufferProtocol */ - address payable public immutable TREASURY; + IGuardianModule public immutable override GUARDIAN_MODULE; /** * @inheritdoc IPufferProtocol */ - IGuardianModule public immutable override GUARDIAN_MODULE; + ValidatorTicket public immutable override VALIDATOR_TICKET; /** * @inheritdoc IPufferProtocol */ - IPufferPool public immutable override POOL; + PufferVaultMainnet public immutable override PUFFER_VAULT; /** * @inheritdoc IPufferProtocol */ - IWithdrawalPool public immutable override WITHDRAWAL_POOL; + IPufferModuleFactory public immutable override PUFFER_MODULE_FACTORY; /** * @inheritdoc IPufferProtocol */ - IPufferModuleFactory public immutable override PUFFER_MODULE_FACTORY; + IPufferOracle public immutable override PUFFER_ORACLE; constructor( - IWithdrawalPool withdrawalPool, - IPufferPool pool, + PufferVaultMainnet pufferVault, IGuardianModule guardianModule, - address payable treasury, - address moduleFactory - ) payable { - TREASURY = treasury; + address moduleFactory, + ValidatorTicket validatorTicket, + IPufferOracle oracle + ) { GUARDIAN_MODULE = guardianModule; - POOL = pool; - WITHDRAWAL_POOL = withdrawalPool; + PUFFER_VAULT = PufferVaultMainnet(payable(address(pufferVault))); PUFFER_MODULE_FACTORY = IPufferModuleFactory(moduleFactory); + VALIDATOR_TICKET = validatorTicket; + PUFFER_ORACLE = oracle; _disableInitializers(); } - receive() external payable { } - - function initialize(address accessManager, address noRestakingModule, uint256[] calldata smoothingCommitments) - external - initializer - { + function initialize(address accessManager, address noRestakingModule) external initializer { __AccessManaged_init(accessManager); - _setProtocolFeeRate(2 * FixedPointMathLib.WAD); // 2% - _setWithdrawalPoolRate(10 * FixedPointMathLib.WAD); // 10 % - _setGuardiansFeeRate(5 * 1e17); // 0.5 % - _setValidatorLimitPerInterval(20); _setValidatorLimitPerModule(_NO_RESTAKING, type(uint128).max); - _setSmoothingCommitments(smoothingCommitments); bytes32[] memory weights = new bytes32[](1); weights[0] = _NO_RESTAKING; _setModuleWeights(weights); _changeModule(_NO_RESTAKING, IPufferModule(noRestakingModule)); - ProtocolStorage storage $ = _getPufferProtocolStorage(); - $.numberOfActiveValidators = uint128(10000); - // Start at 1 (gas optimisation) - $.numberOfValidatorsRegisteredInThisInterval = uint16(1); + _changeMinimumVTAmount(28 ether); // 28 days } /** * @inheritdoc IPufferProtocol + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function registerValidatorKeyPermit( - ValidatorKeyData calldata data, - bytes32 moduleName, - uint256 numberOfMonths, - Permit calldata permit - ) external payable restricted { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - - _checkValidatorRegistrationInputs({ $: $, data: data, moduleName: moduleName }); - - // panic for invalid `numberOfMonths` - uint256 smoothingCommitment = $.smoothingCommitments[numberOfMonths - 1]; - - // SC is paid in ETH - if (msg.value != smoothingCommitment) { - revert InvalidETHAmount(); - } - - // Bond can be in pufETH - uint256 validatorBond = data.raveEvidence.length > 0 ? _ENCLAVE_VALIDATOR_BOND : _NO_ENCLAVE_VALIDATOR_BOND; - - if (POOL.calculatePufETHtoETHAmount(permit.amount) < validatorBond) { - revert InvalidETHAmount(); - } - - try IERC20Permit(address(POOL)).permit({ - owner: permit.owner, + function depositValidatorTickets(Permit calldata permit, address node) external restricted { + try IERC20Permit(address(VALIDATOR_TICKET)).permit({ + owner: msg.sender, spender: address(this), value: permit.amount, deadline: permit.deadline, @@ -167,184 +125,158 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad }) { } catch { } // slither-disable-next-line unchecked-transfer - POOL.transferFrom(msg.sender, address(this), permit.amount); + VALIDATOR_TICKET.transferFrom(msg.sender, address(this), permit.amount); - _storeValidatorInformation({ - $: $, - data: data, - pufETHAmount: permit.amount, - moduleName: moduleName, - numberOfMonths: numberOfMonths - }); + ProtocolStorage storage $ = _getPufferProtocolStorage(); + + _updateVTBalance($, node, 0); - // We've received bond in pufETH, so the second param is 0 - _transferFunds($, 0); + $.nodeOperatorInfo[node].vtBalance += SafeCastLib.toUint96(permit.amount); + emit ValidatorTicketsDeposited(node, msg.sender, permit.amount); } /** * @inheritdoc IPufferProtocol + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function registerValidatorKey(ValidatorKeyData calldata data, bytes32 moduleName, uint256 numberOfMonths) - external - payable - restricted - { + function withdrawValidatorTickets(uint96 amount, address recipient) external restricted { ProtocolStorage storage $ = _getPufferProtocolStorage(); - _checkValidatorRegistrationInputs({ $: $, data: data, moduleName: moduleName }); - - // panic for invalid `numberOfMonths` - uint256 smoothingCommitment = $.smoothingCommitments[numberOfMonths - 1]; - uint256 validatorBond = data.raveEvidence.length > 0 ? _ENCLAVE_VALIDATOR_BOND : _NO_ENCLAVE_VALIDATOR_BOND; - - if (msg.value != (smoothingCommitment + validatorBond)) { - revert InvalidETHAmount(); - } - - // slither-disable-next-line arbitrary-send-eth - uint256 pufETHReceived = POOL.depositETH{ value: validatorBond }(); - - _storeValidatorInformation({ - $: $, - data: data, - pufETHAmount: pufETHReceived, - moduleName: moduleName, - numberOfMonths: numberOfMonths - }); - - // Deduct validatorBond from msg.value - _transferFunds($, validatorBond); - } + $.nodeOperatorInfo[msg.sender].vtBalance -= amount; - function _storeValidatorInformation( - ProtocolStorage storage $, - ValidatorKeyData calldata data, - uint256 pufETHAmount, - bytes32 moduleName, - uint256 numberOfMonths - ) internal { - uint256 validatorIndex = $.pendingValidatorIndices[moduleName]; - - // No need for SafeCast - $.validators[moduleName][validatorIndex] = Validator({ - pubKey: data.blsPubKey, - signature: data.signature, - status: Status.PENDING, - module: address($.modules[moduleName]), - bond: uint64(pufETHAmount), - monthsCommitted: uint24(numberOfMonths), - node: msg.sender - }); + _updateVTBalance($, msg.sender, 0); - // Increment indices for this module and number of validators registered - unchecked { - ++$.pendingValidatorIndices[moduleName]; - ++$.moduleLimits[moduleName].numberOfActiveValidators; - ++$.numberOfValidatorsRegisteredInThisInterval; - ++$.activePufferValidators; + // The user must have at least `minimumVtAmount` VT for each active validator + uint256 mandatoryVTAmount = ( + $.nodeOperatorInfo[msg.sender].activeValidatorCount + $.nodeOperatorInfo[msg.sender].pendingValidatorCount + ) * $.minimumVtAmount; + // If the remaining VT balance is less than the mandatory amount, revert + if ($.nodeOperatorInfo[msg.sender].vtBalance < mandatoryVTAmount) { + revert InvalidValidatorTicketAmount(amount, mandatoryVTAmount); } - emit ValidatorKeyRegistered(data.blsPubKey, validatorIndex, moduleName, (data.raveEvidence.length > 0)); - } + // slither-disable-next-line unchecked-transfer + VALIDATOR_TICKET.transfer(recipient, amount); - /** - * @inheritdoc IPufferProtocol - */ - function getDepositDataRoot(bytes calldata pubKey, bytes calldata signature, bytes calldata withdrawalCredentials) - external - pure - returns (bytes32) - { - return LibBeaconchainContract.getDepositDataRoot(pubKey, signature, withdrawalCredentials); + emit ValidatorTicketsWithdrawn(msg.sender, recipient, amount); } /** * @inheritdoc IPufferProtocol + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function provisionNode(bytes[] calldata guardianEnclaveSignatures) external { + function registerValidatorKey( + ValidatorKeyData calldata data, + bytes32 moduleName, + uint256 numberOfDays, + Permit calldata pufETHPermit, + Permit calldata vtPermit + ) external payable restricted { ProtocolStorage storage $ = _getPufferProtocolStorage(); - (bytes32 moduleName, uint256 index) = getNextValidatorToProvision(); - - bytes memory validatorPubKey = $.validators[moduleName][index].pubKey; - bytes memory validatorSignature = $.validators[moduleName][index].signature; + // Upscale number of days to 18 decimals + if ((numberOfDays * 1 ether) < $.minimumVtAmount) { + revert InvalidData(); + } - // Increment next validator to be provisioned index, overflows if there is no validator for provisioning - $.nextToBeProvisioned[moduleName] = index + 1; - unchecked { - // Increment module selection index - ++$.moduleSelectIndex; + // Revert if the permit amounts are non zero, but the msg.value is also non zero + if (vtPermit.amount != 0 && pufETHPermit.amount != 0 && msg.value > 0) { + revert InvalidETHAmount(); } - bytes memory withdrawalCredentials = getWithdrawalCredentials($.validators[moduleName][index].module); + _checkValidatorRegistrationInputs({ $: $, data: data, moduleName: moduleName }); - bytes32 depositDataRoot = this.getDepositDataRoot({ - pubKey: validatorPubKey, - signature: validatorSignature, - withdrawalCredentials: withdrawalCredentials - }); + uint256 validatorBond = data.raveEvidence.length > 0 ? _ENCLAVE_VALIDATOR_BOND : _NO_ENCLAVE_VALIDATOR_BOND; + // convertToShares is rounding down, @todo double check if we care for this case + uint256 bondInPufETH = PUFFER_VAULT.convertToShares(validatorBond); + uint256 vtPayment = PUFFER_ORACLE.getValidatorTicketPrice() * numberOfDays; - // Check the signatures (reverts if invalid) - GUARDIAN_MODULE.validateProvisionNode({ - pubKey: validatorPubKey, - signature: validatorSignature, - depositDataRoot: depositDataRoot, - withdrawalCredentials: withdrawalCredentials, - guardianEnclaveSignatures: guardianEnclaveSignatures - }); + // If the user overpaid + if (msg.value > (validatorBond + vtPayment)) { + revert InvalidETHAmount(); + } - $.validators[moduleName][index].status = Status.ACTIVE; + uint256 pufETHMinted; - IPufferModule module = $.modules[moduleName]; + // If the VT permit amount is zero, that means that the user is paying for VT with ETH + if (vtPermit.amount == 0) { + VALIDATOR_TICKET.purchaseValidatorTicket{ value: vtPayment }(address(this)); + } else { + _callPermit(address(VALIDATOR_TICKET), vtPermit); + // slither-disable-next-line unchecked-transfer + VALIDATOR_TICKET.transferFrom(msg.sender, address(this), numberOfDays * 1 ether); // * 1 ether is to upscale amount to 18 decimals + } - // Transfer 32 ETH to the module - POOL.transferETH(address(module), 32 ether); + // If the pufETH permit amount is zero, that means that the user is paying the bond with ETH + if (pufETHPermit.amount == 0) { + pufETHMinted = PUFFER_VAULT.depositETH{ value: validatorBond }(address(this)); + } else { + _callPermit(address(PUFFER_VAULT), pufETHPermit); + // slither-disable-next-line unchecked-transfer + PUFFER_VAULT.transferFrom(msg.sender, address(this), bondInPufETH); + } - emit SuccessfullyProvisioned(validatorPubKey, index, moduleName); + // Store the bond amount + uint256 bondAmount = pufETHMinted > 0 ? pufETHMinted : bondInPufETH; - module.callStake({ pubKey: validatorPubKey, signature: validatorSignature, depositDataRoot: depositDataRoot }); + _storeValidatorInformation({ + $: $, + data: data, + pufETHAmount: bondAmount, + moduleName: moduleName, + numberOfDays: numberOfDays + }); } /** * @inheritdoc IPufferProtocol */ - function extendCommitment(bytes32 moduleName, uint256 validatorIndex, uint256 numberOfMonths) external payable { + function provisionNode(bytes[] calldata guardianEnclaveSignatures, uint88 vtBurnOffset) external { ProtocolStorage storage $ = _getPufferProtocolStorage(); - Validator storage validator = $.validators[moduleName][validatorIndex]; - // Causes panic for invalid numberOfMonths - uint256 smoothingCommitment = $.smoothingCommitments[numberOfMonths - 1]; + (bytes32 moduleName, uint256 index) = getNextValidatorToProvision(); - // Node operator can purchase commitment for multiple months - if ((msg.value != smoothingCommitment)) { - revert InvalidETHAmount(); + // Increment next validator to be provisioned index, panics if there is no validator for provisioning + $.nextToBeProvisioned[moduleName] = index + 1; + unchecked { + // Increment module selection index + ++$.moduleSelectIndex; } + // Validator Tickets Accounting + _provisionNodeVTUpdate({ $: $, moduleName: moduleName, index: index, vtQueueOffset: vtBurnOffset }); - // No need for Safecast because of the validations above - validator.monthsCommitted = uint24(numberOfMonths); - - emit SmoothingCommitmentPaid(validator.pubKey, msg.value); + _validateSignaturesAndProvisionValidator({ + $: $, + moduleName: moduleName, + index: index, + vtBurnOffset: vtBurnOffset, + guardianEnclaveSignatures: guardianEnclaveSignatures + }); - _transferFunds($, 0); + // Mark the validator as active + $.validators[moduleName][index].status = Status.ACTIVE; } /** * @inheritdoc IPufferProtocol */ - function stopRegistration(bytes32 moduleName, uint256 validatorIndex) external { + function cancelRegistration(bytes32 moduleName, uint256 validatorIndex) external { ProtocolStorage storage $ = _getPufferProtocolStorage(); // `msg.sender` is the Node Operator Validator storage validator = $.validators[moduleName][validatorIndex]; + address node = validator.node; if (validator.status != Status.PENDING) { revert InvalidValidatorState(validator.status); } - if (msg.sender != validator.node) { + if (msg.sender != node) { revert Unauthorized(); } + _penalizeNodeOperator(node); + // Update the status to DEQUEUED validator.status = Status.DEQUEUED; @@ -359,37 +291,38 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad } // slither-disable-next-line unchecked-transfer - POOL.transfer(validator.node, validator.bond); + PUFFER_VAULT.transfer(node, validator.bond); } /** - * @notice Submit a valid MerkleProof and get back the Bond deposited if the validator was not slashed - * @dev Anybody can trigger a validator exit as long as the proofs submitted are valid + * @inheritdoc IPufferProtocol */ - function stopValidator( - bytes32 moduleName, - uint256 validatorIndex, - uint256 blockNumber, - uint256 withdrawalAmount, - bool wasSlashed, - bytes32[] calldata merkleProof - ) external { + function retrieveBond(StoppedValidatorInfo calldata validatorInfo, bytes32[] calldata merkleProof) external { ProtocolStorage storage $ = _getPufferProtocolStorage(); - Validator storage validator = $.validators[moduleName][validatorIndex]; + Validator storage validator = $.validators[validatorInfo.moduleName][validatorInfo.validatorIndex]; if (validator.status != Status.ACTIVE) { revert InvalidValidatorState(validator.status); } - bytes32 withdrawalRoot = $.fullWithdrawalsRoots[blockNumber]; - if ( // Leaf !MerkleProof.verifyCalldata( merkleProof, - withdrawalRoot, - keccak256(bytes.concat(keccak256(abi.encode(moduleName, validatorIndex, withdrawalAmount, wasSlashed)))) + $.fullWithdrawalsRoots[validatorInfo.blockNumber], + keccak256( + bytes.concat( + keccak256( + abi.encode( + validatorInfo.moduleName, + validatorInfo.validatorIndex, + validatorInfo.withdrawalAmount, + validatorInfo.wasSlashed + ) + ) + ) + ) ) ) { revert InvalidMerkleProof(); @@ -399,33 +332,39 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad address node = validator.node; bytes memory pubKey = validator.pubKey; + _updateStopValidatorVTBalance({ + $: $, + moduleName: validatorInfo.moduleName, + index: validatorInfo.validatorIndex, + validatorStopTimestamp: validatorInfo.validatorStopTimestamp + }); // Remove what we don't delete validator.module; delete validator.node; - delete validator.monthsCommitted; delete validator.bond; delete validator.pubKey; delete validator.signature; validator.status = Status.EXITED; - $.activePufferValidators -= 1; - $.moduleLimits[moduleName].numberOfActiveValidators -= 1; + // Decrease the validator number for that module + $.moduleLimits[validatorInfo.moduleName].numberOfActiveValidators -= 1; // Burn everything if the validator was slashed - if (wasSlashed) { - POOL.burn(returnAmount); + if (validatorInfo.wasSlashed) { + PUFFER_VAULT.burn(returnAmount); } else { uint256 burnAmount = 0; - if (withdrawalAmount < 32 ether) { - burnAmount = POOL.calculateETHToPufETHAmount(32 ether - withdrawalAmount); - POOL.burn(burnAmount); + if (validatorInfo.withdrawalAmount < 32 ether) { + //@todo rounding down, recheck + burnAmount = PUFFER_VAULT.previewDeposit(32 ether - validatorInfo.withdrawalAmount); + PUFFER_VAULT.burn(burnAmount); } // slither-disable-next-line unchecked-transfer - POOL.transfer(node, (returnAmount - burnAmount)); + PUFFER_VAULT.transfer(node, (returnAmount - burnAmount)); } - emit ValidatorExited(pubKey, validatorIndex, moduleName); + emit ValidatorExited(pubKey, validatorInfo.validatorIndex, validatorInfo.moduleName); } /** @@ -435,6 +374,8 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad ProtocolStorage storage $ = _getPufferProtocolStorage(); uint256 skippedIndex = $.nextToBeProvisioned[moduleName]; + address node = $.validators[moduleName][skippedIndex].node; + // Check the signatures (reverts if invalid) GUARDIAN_MODULE.validateSkipProvisioning({ moduleName: moduleName, @@ -442,12 +383,14 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad guardianEOASignatures: guardianEOASignatures }); + _penalizeNodeOperator(node); + // Change the status of that validator $.validators[moduleName][skippedIndex].status = Status.SKIPPED; // Transfer pufETH to that node operator // slither-disable-next-line unchecked-transfer - POOL.transfer($.validators[moduleName][skippedIndex].node, $.validators[moduleName][skippedIndex].bond); + PUFFER_VAULT.transfer(node, $.validators[moduleName][skippedIndex].bond); unchecked { ++$.nextToBeProvisioned[moduleName]; @@ -485,20 +428,10 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad $.fullWithdrawalsRoots[blockNumber] = root; - // Allocate ETH capital back to the pool ASAP to fuel pool growth + // Allocate ETH capital back to the Vault ASAP to fuel Vault growth for (uint256 i = 0; i < modules.length; ++i) { - uint256 amount = amounts[i]; - - uint256 withdrawalPoolAmount = FixedPointMathLib.fullMulDiv(amount, $.withdrawalPoolRate, _ONE_HUNDRED_WAD); - uint256 pufferPoolAmount = amount - withdrawalPoolAmount; - - // Withdrawal pool / pool don't revert on receive() - // slither-disable-next-line calls-loop - IPufferModule(modules[i]).call(address(WITHDRAWAL_POOL), withdrawalPoolAmount, ""); - - // slither-disable-next-line calls-loop - IPufferModule(modules[i]).call(address(POOL), pufferPoolAmount, ""); + IPufferModule(modules[i]).call(address(PUFFER_VAULT), amounts[i], ""); } emit FullWithdrawalsRootPosted(blockNumber, root); @@ -506,6 +439,7 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad /** * @inheritdoc IPufferProtocol + * @dev Restricted to the DAO */ function changeModule(bytes32 moduleName, IPufferModule newModule) external restricted { _changeModule(moduleName, newModule); @@ -513,42 +447,10 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad /** * @inheritdoc IPufferProtocol + * @dev Restricted to the DAO */ - function proofOfReserve( - uint256 ethAmount, - uint256 lockedETH, - uint256 pufETHTotalSupply, - uint256 blockNumber, - uint256 numberOfActiveValidators, - bytes[] calldata guardianSignatures - ) external { - PufferPoolStorage storage $ = _getPufferPoolStorage(); - - // Check the signatures (reverts if invalid) - GUARDIAN_MODULE.validateProofOfReserve({ - ethAmount: ethAmount, - lockedETH: lockedETH, - pufETHTotalSupply: pufETHTotalSupply, - blockNumber: blockNumber, - numberOfActiveValidators: numberOfActiveValidators, - guardianSignatures: guardianSignatures - }); - - if ((block.number - $.lastUpdate) < _UPDATE_INTERVAL) { - revert OutsideUpdateWindow(); - } - - $.ethAmount = ethAmount; - $.lockedETH = lockedETH; - $.pufETHTotalSupply = pufETHTotalSupply; - $.lastUpdate = blockNumber; - - ProtocolStorage storage protocolStorage = _getPufferProtocolStorage(); - // gas optimization to skip zero value - protocolStorage.numberOfValidatorsRegisteredInThisInterval = 1; - protocolStorage.numberOfActiveValidators = uint128(numberOfActiveValidators); - - emit BackingUpdated(ethAmount, lockedETH, pufETHTotalSupply, blockNumber); + function changeMinimumVTAmount(uint256 newMinimumVTAmount) external restricted { + _changeMinimumVTAmount(newMinimumVTAmount); } /** @@ -569,41 +471,6 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad _setModuleWeights(newModuleWeights); } - /** - * @inheritdoc IPufferProtocol - */ - function setValidatorLimitPerInterval(uint256 newLimit) external restricted { - _setValidatorLimitPerInterval(newLimit); - } - - /** - * @inheritdoc IPufferProtocol - */ - function setSmoothingCommitments(uint256[] calldata smoothingCommitments) external restricted { - _setSmoothingCommitments(smoothingCommitments); - } - - /** - * @inheritdoc IPufferProtocol - */ - function setProtocolFeeRate(uint256 protocolFeeRate) external restricted { - _setProtocolFeeRate(protocolFeeRate); - } - - /** - * @inheritdoc IPufferProtocol - */ - function setGuardiansFeeRate(uint256 newRate) external restricted { - _setGuardiansFeeRate(newRate); - } - - /** - * @inheritdoc IPufferProtocol - */ - function setWithdrawalPoolRate(uint256 newRate) external restricted { - _setWithdrawalPoolRate(newRate); - } - /** * @inheritdoc IPufferProtocol */ @@ -614,9 +481,12 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad /** * @inheritdoc IPufferProtocol */ - function getValidatorLimitPerInterval() external view returns (uint256) { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - return uint256($.validatorLimitPerInterval); + function getDepositDataRoot(bytes calldata pubKey, bytes calldata signature, bytes calldata withdrawalCredentials) + external + pure + returns (bytes32) + { + return LibBeaconchainContract.getDepositDataRoot(pubKey, signature, withdrawalCredentials); } /** @@ -636,14 +506,6 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad return validators; } - /** - * @inheritdoc IPufferProtocol - */ - function getSmoothingCommitment(uint256 numberOfMonths) external view returns (uint256) { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - return $.smoothingCommitments[numberOfMonths - 1]; - } - /** * @inheritdoc IPufferProtocol */ @@ -708,17 +570,17 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad /** * @inheritdoc IPufferProtocol */ - function getModuleAddress(bytes32 moduleName) external view returns (address) { + function getNodeInfo(address node) external view returns (NodeInfo memory) { ProtocolStorage storage $ = _getPufferProtocolStorage(); - return address($.modules[moduleName]); + return $.nodeOperatorInfo[node]; } /** * @inheritdoc IPufferProtocol */ - function getProtocolFeeRate() external view returns (uint256) { + function getModuleAddress(bytes32 moduleName) external view returns (address) { ProtocolStorage storage $ = _getPufferProtocolStorage(); - return $.protocolFeeRate; + return address($.modules[moduleName]); } /** @@ -744,10 +606,37 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad return $.moduleSelectIndex; } + /** + * @inheritdoc IPufferProtocol + */ + function getValidatorTicketsBalance(address owner) public view returns (uint256) { + ProtocolStorage storage $ = _getPufferProtocolStorage(); + + NodeInfo memory nodeInfo = $.nodeOperatorInfo[owner]; + + // We only care about the time difference + uint256 elapsedTime = block.timestamp > nodeInfo.lastUpdate + ? block.timestamp - nodeInfo.lastUpdate + : nodeInfo.lastUpdate - block.timestamp; + + uint256 calculatedBalance = (nodeInfo.vtBalance + nodeInfo.virtualVTBalance) + - (_VT_LOSS_RATE_PER_SECOND * elapsedTime * nodeInfo.activeValidatorCount); + + return calculatedBalance; + } + + /** + * @inheritdoc IPufferProtocol + */ + function getMinimumVtAmount() public view returns (uint256) { + ProtocolStorage storage $ = _getPufferProtocolStorage(); + return $.minimumVtAmount; + } + /** * @notice Returns necessary information to make Guardian's life easier */ - function getPayload(bytes32 moduleName, bool usingEnclave, uint256 numberOfMonths) + function getPayload(bytes32 moduleName, bool usingEnclave, uint256 numberOfDays) external view returns (bytes[] memory, bytes memory, uint256, uint256) @@ -758,86 +647,52 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad bytes memory withdrawalCredentials = getWithdrawalCredentials(address($.modules[moduleName])); uint256 threshold = GUARDIAN_MODULE.getThreshold(); uint256 validatorBond = usingEnclave ? _ENCLAVE_VALIDATOR_BOND : _NO_ENCLAVE_VALIDATOR_BOND; - uint256 ethAmount = validatorBond + $.smoothingCommitments[numberOfMonths - 1]; + uint256 ethAmount = validatorBond + PUFFER_ORACLE.getValidatorTicketPrice() * numberOfDays; return (pubKeys, withdrawalCredentials, threshold, ethAmount); } - function _setSmoothingCommitments(uint256[] calldata smoothingCommitments) internal { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256[] memory oldSmoothingCommitments = $.smoothingCommitments; - $.smoothingCommitments = smoothingCommitments; - emit CommitmentsChanged(oldSmoothingCommitments, smoothingCommitments); - } - - function _transferFunds(ProtocolStorage storage $, uint256 bond) internal { - uint256 amount = msg.value - bond; + function _storeValidatorInformation( + ProtocolStorage storage $, + ValidatorKeyData calldata data, + uint256 pufETHAmount, + bytes32 moduleName, + uint256 numberOfDays + ) internal { + uint256 validatorIndex = $.pendingValidatorIndices[moduleName]; - // If we are above burst threshold, take everything to the treasury - // this number division doesn't revert - if (($.activePufferValidators * 100 / $.numberOfActiveValidators) > _BURST_THRESHOLD) { - _sendETH(TREASURY, amount, _ONE_HUNDRED_WAD); - return; - } + // No need for SafeCast + $.validators[moduleName][validatorIndex] = Validator({ + pubKey: data.blsPubKey, + signature: data.signature, + status: Status.PENDING, + module: address($.modules[moduleName]), + bond: uint64(pufETHAmount), + node: msg.sender + }); - uint256 treasuryAmount = _sendETH(TREASURY, amount, $.protocolFeeRate); - uint256 guardiansAmount = _sendETH(address(GUARDIAN_MODULE), amount, $.guardiansFeeRate); + $.nodeOperatorInfo[msg.sender].vtBalance += SafeCastLib.toUint96(numberOfDays * 1 ether); // upscale to 18 decimals - uint256 remainder = amount - (treasuryAmount + guardiansAmount); + // Increment indices for this module and number of validators registered + unchecked { + ++$.nodeOperatorInfo[msg.sender].pendingValidatorCount; + ++$.pendingValidatorIndices[moduleName]; + ++$.moduleLimits[moduleName].numberOfActiveValidators; + } - uint256 withdrawalPoolAmount = _sendETH(address(WITHDRAWAL_POOL), remainder, $.withdrawalPoolRate); - address(POOL).safeTransferETH(remainder - withdrawalPoolAmount); + emit ValidatorKeyRegistered(data.blsPubKey, validatorIndex, moduleName, (data.raveEvidence.length > 0)); } function _setValidatorLimitPerModule(bytes32 moduleName, uint128 limit) internal { ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256 oldLimit = $.moduleLimits[moduleName].allowedLimit; + emit ValidatorLimitPerModuleChanged($.moduleLimits[moduleName].allowedLimit, limit); $.moduleLimits[moduleName].allowedLimit = limit; - emit ValidatorLimitPerModuleChanged(oldLimit, limit); - } - - function _setGuardiansFeeRate(uint256 newRate) internal { - if (newRate > (2 * FixedPointMathLib.WAD)) { - revert InvalidData(); - } - ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256 oldRate = $.guardiansFeeRate; - $.guardiansFeeRate = SafeCastLib.toUint72(newRate); - emit GuardiansFeeRateChanged(oldRate, newRate); - } - - // _sendETH is sending ETH to trusted addresses (no reentrancy) - function _sendETH(address to, uint256 amount, uint256 rate) internal returns (uint256 toSend) { - toSend = FixedPointMathLib.fullMulDiv(amount, rate, _ONE_HUNDRED_WAD); - - if (toSend != 0) { - emit TransferredETH(to, toSend); - to.safeTransferETH(toSend); - } } function _setModuleWeights(bytes32[] memory newModuleWeights) internal { ProtocolStorage storage $ = _getPufferProtocolStorage(); - bytes32[] memory oldModuleWeights = $.moduleWeights; + emit ModuleWeightsChanged($.moduleWeights, newModuleWeights); $.moduleWeights = newModuleWeights; - emit ModuleWeightsChanged(oldModuleWeights, newModuleWeights); - } - - function _setProtocolFeeRate(uint256 protocolFee) internal { - if (protocolFee > (10 * FixedPointMathLib.WAD)) { - revert InvalidData(); - } - ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256 oldProtocolFee = $.protocolFeeRate; - $.protocolFeeRate = SafeCastLib.toUint72(protocolFee); - emit ProtocolFeeRateChanged(oldProtocolFee, protocolFee); - } - - function _setWithdrawalPoolRate(uint256 withdrawalPoolRate) internal { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256 oldWithdrawalPoolRate = $.withdrawalPoolRate; - $.withdrawalPoolRate = SafeCastLib.toUint72(withdrawalPoolRate); - emit WithdrawalPoolRateChanged(oldWithdrawalPoolRate, withdrawalPoolRate); } function _createPufferModule(bytes32 moduleName, string calldata metadataURI, address delegationApprover) @@ -855,23 +710,11 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad return address(module); } - function _setValidatorLimitPerInterval(uint256 newLimit) internal { - ProtocolStorage storage $ = _getPufferProtocolStorage(); - uint256 oldLimit = uint256($.validatorLimitPerInterval); - $.validatorLimitPerInterval = SafeCastLib.toUint16(newLimit); - emit ValidatorLimitPerIntervalChanged(oldLimit, newLimit); - } - function _checkValidatorRegistrationInputs( ProtocolStorage storage $, ValidatorKeyData calldata data, bytes32 moduleName ) internal view { - // validatorLimitPerInterval starts at 1, and +1 is to include check if the current registration will go over the limit - if (($.numberOfValidatorsRegisteredInThisInterval + 1) > $.validatorLimitPerInterval + 1) { - revert ValidatorLimitPerIntervalReached(); - } - // This acts as a validation if the module is existent // +1 is to validate the current transaction registration if (($.moduleLimits[moduleName].numberOfActiveValidators + 1) > $.moduleLimits[moduleName].allowedLimit) { @@ -901,5 +744,172 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad emit ModuleChanged(moduleName, address(oldModule), address(newModule)); } + function _changeMinimumVTAmount(uint256 newMinimumVtAmount) internal { + ProtocolStorage storage $ = _getPufferProtocolStorage(); + emit MinimumVTAmountChanged($.minimumVtAmount, newMinimumVtAmount); + $.minimumVtAmount = newMinimumVtAmount; + } + + function _validateSignaturesAndProvisionValidator( + ProtocolStorage storage $, + bytes32 moduleName, + uint256 index, + uint256 vtBurnOffset, + bytes[] calldata guardianEnclaveSignatures + ) internal { + bytes memory validatorPubKey = $.validators[moduleName][index].pubKey; + bytes memory validatorSignature = $.validators[moduleName][index].signature; + + bytes memory withdrawalCredentials = getWithdrawalCredentials($.validators[moduleName][index].module); + + bytes32 depositDataRoot = this.getDepositDataRoot({ + pubKey: validatorPubKey, + signature: validatorSignature, + withdrawalCredentials: withdrawalCredentials + }); + + // Check the signatures (reverts if invalid) + GUARDIAN_MODULE.validateProvisionNode({ + validatorIndex: index, + vtBurnOffset: vtBurnOffset, + pubKey: validatorPubKey, + signature: validatorSignature, + depositDataRoot: depositDataRoot, + withdrawalCredentials: withdrawalCredentials, + guardianEnclaveSignatures: guardianEnclaveSignatures + }); + + IPufferModule module = $.modules[moduleName]; + + // Transfer 32 ETH to the module + PUFFER_VAULT.transferETH(address(module), 32 ether); + + emit SuccessfullyProvisioned(validatorPubKey, index, moduleName); + + // Increase lockedETH on Puffer Oracle + PUFFER_ORACLE.provisionNode(); + module.callStake({ pubKey: validatorPubKey, signature: validatorSignature, depositDataRoot: depositDataRoot }); + } + + function _penalizeNodeOperator(address node) internal { + // Burn 10 days of VT's from the node + VALIDATOR_TICKET.burn(10 ether); + + ProtocolStorage storage $ = _getPufferProtocolStorage(); + $.nodeOperatorInfo[node].vtBalance -= 10 ether; + --$.nodeOperatorInfo[node].pendingValidatorCount; + } + + /** + * @dev When the node operator registers a new validator, the VT balance is updated + * Because the entry queue varies, the guardians will credit node operator with the virtual VT's. + * That means that the VT decay will start when the provisioning happens, but because the node operator is credited + * Virtual VT's by the guardians, the end result will be the same. + */ + function _provisionNodeVTUpdate(ProtocolStorage storage $, bytes32 moduleName, uint256 index, uint88 vtQueueOffset) + internal + { + address node = $.validators[moduleName][index].node; + + _updateVTBalance($, node, vtQueueOffset); + + --$.nodeOperatorInfo[node].pendingValidatorCount; + ++$.nodeOperatorInfo[node].activeValidatorCount; + } + + /** + * @dev When the node operator gets ejected / exits a validator, the node operator continues to lose VT. + * When the retrieveBond is called, we will credit that node operator with virtual VT's that can't be redeemed. + * If the node operator wants to retrieve unspent VT's and the bond, they are incentivized to do so as soon as possible. + */ + function _updateStopValidatorVTBalance( + ProtocolStorage storage $, + bytes32 moduleName, + uint256 index, + uint256 validatorStopTimestamp + ) internal { + address node = $.validators[moduleName][index].node; + + uint88 vtToCredit = 0; + + // If the lastUpdate is bigger, we need to credit the node operator with virtual VT's, because we counted his validator as `active` and burned his VT + if (validatorStopTimestamp < $.nodeOperatorInfo[node].lastUpdate) { + vtToCredit = SafeCastLib.toUint88( + ($.nodeOperatorInfo[node].lastUpdate - validatorStopTimestamp) * _VT_LOSS_RATE_PER_SECOND_DOWN + ); + } + + // But we credit the `vtToCredit` to the node operator + _updateVTBalance($, node, vtToCredit); + + $.nodeOperatorInfo[node].activeValidatorCount -= 1; + } + + function _updateVTBalance(ProtocolStorage storage $, address node, uint88 vtQueueOffset) internal { + uint256 oldVTBalance = $.nodeOperatorInfo[node].vtBalance; + uint256 oldVirtualVTBalance = $.nodeOperatorInfo[node].virtualVTBalance; + + uint256 totalOldVTBalance = oldVTBalance + oldVirtualVTBalance; + + // Returns the new total balance + uint256 newVTBalance = getValidatorTicketsBalance(node); + + $.nodeOperatorInfo[node].virtualVTBalance += vtQueueOffset; + + uint256 burnedAmount = _burnVt($, node, totalOldVTBalance, newVTBalance); + + uint256 realVTBalance = oldVTBalance - burnedAmount; + + // Update the node information + $.nodeOperatorInfo[node].lastUpdate = uint48(block.timestamp); + $.nodeOperatorInfo[node].vtBalance = SafeCastLib.toUint96(realVTBalance); + emit VTBalanceChanged({ + node: node, + oldVTBalance: oldVTBalance, + newVTBalance: realVTBalance, + oldVirtualVTBalance: oldVirtualVTBalance, + newVirtualVTBalance: $.nodeOperatorInfo[node].virtualVTBalance + }); + } + + /** + * @dev Burns the VT's from `node` and returns the amount burned + * newVTBalance can be bigger than `totalOldVTBalance` because of the virtual VT's that we give to the node operator + */ + function _burnVt(ProtocolStorage storage $, address node, uint256 totalOldVTBalance, uint256 newVTBalance) + internal + returns (uint256) + { + // The diff is the amount to burn + uint256 toBurn = + totalOldVTBalance > newVTBalance ? (totalOldVTBalance - newVTBalance) : (newVTBalance - totalOldVTBalance); + + uint256 virtualVTBalance = $.nodeOperatorInfo[node].virtualVTBalance; + + // First, try to deduct from the virtual VT balance + if (toBurn <= virtualVTBalance) { + $.nodeOperatorInfo[node].virtualVTBalance -= SafeCastLib.toUint88(toBurn); + return 0; + } + + // If the virtual VT balance is not enough, we first deduct from the virtual VT balance, and then burn from the VT balance + toBurn -= virtualVTBalance; + $.nodeOperatorInfo[node].virtualVTBalance = 0; + VALIDATOR_TICKET.burn(toBurn); + return toBurn; + } + + function _callPermit(address token, Permit calldata permitData) internal { + try IERC20Permit(token).permit({ + owner: msg.sender, + spender: address(this), + value: permitData.amount, + deadline: permitData.deadline, + v: permitData.v, + s: permitData.s, + r: permitData.r + }) { } catch { } + } + function _authorizeUpgrade(address newImplementation) internal virtual override restricted { } } diff --git a/src/PufferProtocolStorage.sol b/src/PufferProtocolStorage.sol index df802e90..79ab72f7 100644 --- a/src/PufferProtocolStorage.sol +++ b/src/PufferProtocolStorage.sol @@ -1,21 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { IPufferProtocolStorage } from "puffer/interface/IPufferProtocolStorage.sol"; import { ProtocolStorage } from "puffer/struct/ProtocolStorage.sol"; -import { PufferPoolStorage } from "puffer/struct/PufferPoolStorage.sol"; /** * @title PufferProtocolStorage * @author Puffer Finance * @custom:security-contact security@puffer.fi */ -abstract contract PufferProtocolStorage is IPufferProtocolStorage { - /** - * @dev Constant representing 100% - */ - uint256 internal constant _ONE_HUNDRED_WAD = 100 * 1e18; // 1e18 = WAD - +abstract contract PufferProtocolStorage { /** * @dev Storage slot location for PufferProtocol * @custom:storage-location erc7201:PufferProtocol.storage @@ -23,29 +16,6 @@ abstract contract PufferProtocolStorage is IPufferProtocolStorage { bytes32 private constant _PUFFER_PROTOCOL_STORAGE = 0xb8d3716136db480afe9a80da6be84f994509ecf9515ed14d03024589b5f2bd00; - /** - * @dev Storage slot location for PufferPool - * @custom:storage-location erc7201:PufferPool.storage - */ - bytes32 private constant _PUFFER_POOL_STORAGE = 0x3d9197675aec7b7f62441149aba7986872b7337d003616efa547249bb6c43900; - - function getPufferPoolStorage() external pure returns (PufferPoolStorage memory) { - PufferPoolStorage storage $; - // solhint-disable-next-line no-inline-assembly - assembly { - $.slot := _PUFFER_POOL_STORAGE - } - - return $; - } - - function _getPufferPoolStorage() internal pure returns (PufferPoolStorage storage $) { - // solhint-disable-next-line no-inline-assembly - assembly { - $.slot := _PUFFER_POOL_STORAGE - } - } - function _getPufferProtocolStorage() internal pure returns (ProtocolStorage storage $) { // solhint-disable-next-line no-inline-assembly assembly { diff --git a/src/TokenRescuer.sol b/src/TokenRescuer.sol index b6657d0b..eea24fb3 100644 --- a/src/TokenRescuer.sol +++ b/src/TokenRescuer.sol @@ -16,6 +16,9 @@ import { IERC721 } from "openzeppelin/token/ERC721/ERC721.sol"; abstract contract TokenRescuer is IERC721Receiver, IERC1155Receiver { using SafeTransferLib for address; + // slither-disable-next-line constable-states + address public to = address(12345); //@todo figure out where to rescue stuff + /** * @notice Address of the Puffer Protocol */ @@ -29,27 +32,21 @@ abstract contract TokenRescuer is IERC721Receiver, IERC1155Receiver { * @notice Transfers ERC20 `token`'s balance to treasury */ function recoverERC20(address token) external virtual { - token.safeTransferAll(PUFFER_PROTOCOL.TREASURY()); + token.safeTransferAll(to); } /** * @notice Transfers ERC721 `token` with `tokenId` to treasury */ function recoverERC721(address token, uint256 tokenId) external virtual { - IERC721(token).safeTransferFrom(address(this), PUFFER_PROTOCOL.TREASURY(), tokenId); + IERC721(token).safeTransferFrom(address(this), to, tokenId); } /** * @notice Transfers ERC1155 `token` with `tokenId` and `tokenAmount` to treasury */ function recoverERC1155(address token, uint256 tokenId, uint256 tokenAmount) external virtual { - IERC1155(token).safeTransferFrom({ - from: address(this), - to: PUFFER_PROTOCOL.TREASURY(), - id: tokenId, - value: tokenAmount, - data: "" - }); + IERC1155(token).safeTransferFrom({ from: address(this), to: to, id: tokenId, value: tokenAmount, data: "" }); } function onERC1155Received(address, address, uint256, uint256, bytes calldata) external virtual returns (bytes4) { diff --git a/src/ValidatorTicket.sol b/src/ValidatorTicket.sol new file mode 100644 index 00000000..6f383cfb --- /dev/null +++ b/src/ValidatorTicket.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { UUPSUpgradeable } from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { AccessManagedUpgradeable } from "openzeppelin-upgradeable/access/manager/AccessManagedUpgradeable.sol"; +import { ERC20PermitUpgradeable } from "openzeppelin-upgrades/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ValidatorTicketStorage } from "src/ValidatorTicketStorage.sol"; +import { SafeCastLib } from "solady/utils/SafeCastLib.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; + +/** + * @title ValidatorTicket + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +contract ValidatorTicket is + ValidatorTicketStorage, + UUPSUpgradeable, + AccessManagedUpgradeable, + ERC20PermitUpgradeable +{ + using SafeERC20 for address; + using SafeTransferLib for address; + using SafeTransferLib for address payable; + + error InvalidAmount(); + + /** + * @dev Puffer Finance treasury + */ + address payable public immutable TREASURY; + + /** + * @notice Emitted when the ETH `amount` in wei is transferred to `to` address + * @dev Signature "0xba7bb5aa419c34d8776b86cc0e9d41e72d74a893a511f361a11af6c05e920c3d" + */ + event TransferredETH(address indexed to, uint256 amount); + + /** + * @notice Emitted when the protocol fee rate is changed + * @dev Signature "0xb51bef650ff5ad43303dbe2e500a74d4fd1bdc9ae05f046bece330e82ae0ba87" + */ + event ProtocolFeeChanged(uint256 oldTreasuryFee, uint256 newTreasuryFee); + + /** + * @notice Emitted when the protocol fee rate is changed + * @dev Signature "0x0a3e0a163d4dfba5f018c5c1e2214007151b3abb0907e3ae402ae447c7e1bc47" + */ + event GuardiansFeeChanged(uint256 oldGuardiansFee, uint256 newGuardiansFee); + + /** + * @notice Thrown if the oracle tries to submit invalid data + * @dev Signature "0x5cb045db" + */ + error InvalidData(); + + IPufferOracle public immutable PUFFER_ORACLE; + + address payable public immutable GUARDIAN_MODULE; + + address payable public immutable PUFFER_VAULT; + + constructor(address payable guardianModule, address payable pufferVault, IPufferOracle pufferOracle) { + PUFFER_ORACLE = pufferOracle; + GUARDIAN_MODULE = guardianModule; + PUFFER_VAULT = pufferVault; + _disableInitializers(); + } + + function initialize(address accessManager, uint256 treasuryFeeRate, uint256 guardiansFeeRate) + external + initializer + { + __AccessManaged_init(accessManager); + __ERC20_init("Puffer Validator Ticket", "VT"); + __ERC20Permit_init("Puffer Validator Ticket"); + _setProtocolFeeRate(treasuryFeeRate); + _setGuardiansFeeRate(guardiansFeeRate); + } + + /** + * @notice Mints sender VT corresponding to sent ETH + * @param recipient The address to mint VT to + * @dev restricted modifier is also used as `whenNotPaused` + * @notice Sends PufferVault due share, holding back rest to later distribute between Treasury and Guardians + */ + function purchaseValidatorTicket(address recipient) external payable restricted { + ValidatorTicket storage $ = _getValidatorTicketStorage(); + + uint256 mintPrice = PUFFER_ORACLE.getValidatorTicketPrice(); + + // We are only accepting deposits in multiples of mintPrice + if (msg.value % mintPrice != 0) { + revert InvalidAmount(); + } + + // slither-disable-next-line divide-before-multiply + _mint(recipient, (msg.value / mintPrice) * 1 ether); // * 1 ether is to upscale amount to 18 decimals + + // If we are over the burst threshold, keep everything + // That means that pufETH holders are not getting any new rewards until it goes under the threshold + if (PUFFER_ORACLE.isOverBurstThreshold()) { + // The remainder belongs to PufferVault + return; + } + + // Treasury amount is staying in this contract + uint256 treasuryAmount = FixedPointMathLib.fullMulDiv(msg.value, $.protocolFeeRate, _ONE_HUNDRED_WAD); + // Guardians get the cut right away + uint256 guardiansAmount = _sendETH(GUARDIAN_MODULE, msg.value, $.guardiansFeeRate); + // The remainder belongs to PufferVault + uint256 pufferVaultAmount = msg.value - (treasuryAmount + guardiansAmount); + PUFFER_VAULT.safeTransferETH(pufferVaultAmount); + } + + /** + * @notice Burns `amount` from the transaction sender + * @dev Signature "0x42966c68" + */ + function burn(uint256 amount) external restricted { + _burn(msg.sender, amount); + } + + /** + * @notice Updates the treasury fee + * @dev Restricted access + * @param newProtocolFeeRate The new treasury fee rate + */ + function setProtocolFeeRate(uint256 newProtocolFeeRate) external restricted { + _setProtocolFeeRate(newProtocolFeeRate); + } + + /** + * @notice Updates the guardians fee rate + * @dev Restricted access + * @param newGuardiansFeeRate The new guardians fee rate + */ + function setGuardiansFeeRate(uint256 newGuardiansFeeRate) external restricted { + _setGuardiansFeeRate(newGuardiansFeeRate); + } + + /** + * @notice Retrieves the current protocol fee rate + * @return The current protocol fee rate + */ + function getProtocolFeeRate() external view returns (uint256) { + ValidatorTicket storage $ = _getValidatorTicketStorage(); + return $.protocolFeeRate; + } + + /** + * @dev _sendETH is sending ETH to trusted addresses (no reentrancy protection) + */ + function _sendETH(address to, uint256 amount, uint256 rate) internal returns (uint256 toSend) { + toSend = FixedPointMathLib.fullMulDiv(amount, rate, _ONE_HUNDRED_WAD); + + if (toSend != 0) { + emit TransferredETH(to, toSend); + to.safeTransferETH(toSend); + } + } + + function _setProtocolFeeRate(uint256 newProtocolFeeRate) internal { + ValidatorTicket storage $ = _getValidatorTicketStorage(); + // Treasury fee can not be bigger than 10% + if ($.protocolFeeRate > (10 * FixedPointMathLib.WAD)) { + revert InvalidData(); + } + uint256 oldProtocolFeeRate = uint256($.protocolFeeRate); + $.protocolFeeRate = SafeCastLib.toUint64(newProtocolFeeRate); + emit ProtocolFeeChanged(oldProtocolFeeRate, newProtocolFeeRate); + } + + function _setGuardiansFeeRate(uint256 newGuardiansFeeRate) internal { + ValidatorTicket storage $ = _getValidatorTicketStorage(); + // Treasury fee can not be bigger than 10% + if ($.protocolFeeRate > (10 * FixedPointMathLib.WAD)) { + revert InvalidData(); + } + uint256 oldGuardiansFeeRate = uint256($.guardiansFeeRate); + $.guardiansFeeRate = SafeCastLib.toUint64(newGuardiansFeeRate); + emit GuardiansFeeChanged(oldGuardiansFeeRate, newGuardiansFeeRate); + } + + function _authorizeUpgrade(address newImplementation) internal virtual override restricted { } +} diff --git a/src/ValidatorTicketStorage.sol b/src/ValidatorTicketStorage.sol new file mode 100644 index 00000000..40a68268 --- /dev/null +++ b/src/ValidatorTicketStorage.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @title ValidatorTicketStorage + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +abstract contract ValidatorTicketStorage { + //@todo optimize struct + struct ValidatorTicket { + /** + * @dev Protocol fee rate, can be updated by governance (1e20 = 100%, 1e18 = 1%) + * Because we are using uint64, that means that the max protocol fee rate is 18.44% + * Slot 1 + */ + uint64 protocolFeeRate; + /** + * @dev Guardians fee rate, can be updated by governance (1e20 = 100%, 1e18 = 1%) + * Because we are using uint64, that means that the max protocol fee rate is 18.44% + * Slot 1 + */ + uint64 guardiansFeeRate; + /** + * @dev ETH amount owned to the Guardians + * Slot 1 + */ + uint72 guardiansBalance; + } + /** + * @dev Constant representing 100% + */ + + uint256 internal constant _ONE_HUNDRED_WAD = 100 * 1e18; // 1e18 = WAD + + /** + * @dev Storage slot location for ValidatorTicket + * @custom:storage-location erc7201:ValidatorTicket.storage + */ + bytes32 private constant _VALIDATOR_TICKET_STORAGE = + 0x522b25b4b3844af9be07fc1b83a538fd31925481b968b15976cafed863007000; + + function _getValidatorTicketStorage() internal pure returns (ValidatorTicket storage $) { + // solhint-disable-next-line no-inline-assembly + assembly { + $.slot := _VALIDATOR_TICKET_STORAGE + } + } +} diff --git a/src/WithdrawalPool.sol b/src/WithdrawalPool.sol deleted file mode 100644 index 92923306..00000000 --- a/src/WithdrawalPool.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; -import { AccessManaged } from "openzeppelin/access/manager/AccessManaged.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -import { Permit } from "puffer/struct/Permit.sol"; - -/** - * @title WithdrawalPool - * @notice Users can burn their pufETH and get ETH from this pool - * @author Puffer Finance - * @custom:security-contact security@puffer.fi - */ -contract WithdrawalPool is IWithdrawalPool, AccessManaged { - using SafeTransferLib for address; - - /** - * @notice PufferPool - */ - PufferPool public immutable POOL; - - /** - * @dev A constant representing `100%` - */ - uint256 internal constant _ONE_HUNDRED_WAD = 100 * FixedPointMathLib.WAD; - - /** - * @dev Withdrawal fee amount - */ - uint256 internal _withdrawalFee = FixedPointMathLib.WAD; // 1% - - constructor(PufferPool pufferPool, address initialAuthority) payable AccessManaged(initialAuthority) { - POOL = pufferPool; - } - - receive() external payable { } - - /** - * @inheritdoc IWithdrawalPool - */ - function withdrawETH(address to, uint256 pufETHAmount) external restricted returns (uint256) { - return _withdrawETH(msg.sender, to, pufETHAmount); - } - - /** - * @inheritdoc IWithdrawalPool - */ - function setWithdrawalFee(uint256 withdrawalFee) external restricted { - // Revert if bigger than 3% - if (withdrawalFee > (3 * FixedPointMathLib.WAD)) { - revert InvalidFeeRate(); - } - uint256 oldRate = _withdrawalFee; - _withdrawalFee = withdrawalFee; - emit WithdrawalFeeChanged(oldRate, withdrawalFee); - } - - /** - * @inheritdoc IWithdrawalPool - */ - function withdrawETH(address to, Permit calldata permit) external restricted returns (uint256) { - // Approve pufETH from owner to this contract - try POOL.permit({ - owner: msg.sender, - spender: address(this), - value: permit.amount, - deadline: permit.deadline, - v: permit.v, - s: permit.s, - r: permit.r - }) { } catch { } - - // Only permit signer (msg.sender) can use the permit - return _withdrawETH(msg.sender, to, permit.amount); - } - - function _withdrawETH(address from, address to, uint256 pufETHAmount) internal returns (uint256) { - // Transfer pufETH from the owner to this contract - // pufETH contract reverts, no need to check for return value - // slither-disable-start arbitrary-send-erc20-permit - // slither-disable-next-line unchecked-transfer - POOL.transferFrom(from, address(this), pufETHAmount); - // slither-disable-end arbitrary-send-erc20-permit - - // Calculate ETH amount - uint256 ethAmount = POOL.calculatePufETHtoETHAmount(pufETHAmount); - - // There is a withdrawal fee that is staying in the WithdrawalPool - // It is not going to treasury, it is distributed to all pufETH holders - uint256 fee = FixedPointMathLib.fullMulDiv(ethAmount, _withdrawalFee, _ONE_HUNDRED_WAD); - - // Burn PufETH - POOL.burn(pufETHAmount); - - uint256 amount = ethAmount - fee; - - to.safeTransferETH(amount); - - return amount; - } -} diff --git a/src/interface/IGuardianModule.sol b/src/interface/IGuardianModule.sol index e857c606..becf2140 100644 --- a/src/interface/IGuardianModule.sol +++ b/src/interface/IGuardianModule.sol @@ -89,6 +89,8 @@ interface IGuardianModule { /** * @notice Validates the node provisioning calldata + * @param validatorIndex is the validator index in Puffer + * @param vtBurnOffset is an offset used such that VTs only burn after the validator is active * @param pubKey The public key * @param signature The signature * @param withdrawalCredentials The withdrawal credentials @@ -96,6 +98,8 @@ interface IGuardianModule { * @param guardianEnclaveSignatures The guardian enclave signatures */ function validateProvisionNode( + uint256 validatorIndex, + uint256 vtBurnOffset, bytes memory pubKey, bytes calldata signature, bytes calldata withdrawalCredentials, @@ -116,19 +120,16 @@ interface IGuardianModule { /** * @notice Validates the proof of reserve * @dev This function validates the proof of reserve by checking the signatures of the guardians - * @param ethAmount The amount of ETH * @param lockedETH The locked ETH amount - * @param pufETHTotalSupply The total supply of PUF-ETH tokens - * @param blockNumber The block number - * @param numberOfActiveValidators is the number of all active validators on Beacon Chain + * @param numberOfActivePufferValidators is the number of active Puffer Validators + * @param totalNumberOfValidators is the number of total Validators * @param guardianSignatures The guardian signatures */ function validateProofOfReserve( - uint256 ethAmount, uint256 lockedETH, - uint256 pufETHTotalSupply, uint256 blockNumber, - uint256 numberOfActiveValidators, + uint256 numberOfActivePufferValidators, + uint256 totalNumberOfValidators, bytes[] calldata guardianSignatures ) external view; diff --git a/src/interface/IPufferProtocol.sol b/src/interface/IPufferProtocol.sol index 74e608cc..27fedbd4 100644 --- a/src/interface/IPufferProtocol.sol +++ b/src/interface/IPufferProtocol.sol @@ -4,26 +4,22 @@ pragma solidity >=0.8.0 <0.9.0; import { Validator } from "puffer/struct/Validator.sol"; import { ValidatorKeyData } from "puffer/struct/ValidatorKeyData.sol"; import { IGuardianModule } from "puffer/interface/IGuardianModule.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; import { IPufferModuleFactory } from "puffer/interface/IPufferModuleFactory.sol"; -import { IPufferPool } from "puffer/interface/IPufferPool.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; -import { IPufferProtocolStorage } from "puffer/interface/IPufferProtocolStorage.sol"; import { Status } from "puffer/struct/Status.sol"; import { Permit } from "puffer/struct/Permit.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import { NodeInfo } from "puffer/struct/NodeInfo.sol"; +import { StoppedValidatorInfo } from "puffer/struct/StoppedValidatorInfo.sol"; /** * @title IPufferProtocol * @author Puffer Finance * @custom:security-contact security@puffer.fi */ -interface IPufferProtocol is IPufferProtocolStorage { - /** - * @notice Thrown when external call failed - * @dev Signature "0x625a40e6" - */ - error Failed(); - +interface IPufferProtocol { /** * @notice Thrown when the number of BLS public key shares doesn't match guardians number * @dev Signature "0x8cdea6a6" @@ -43,10 +39,10 @@ interface IPufferProtocol is IPufferProtocolStorage { error ModuleAlreadyExists(); /** - * @notice Thrown when the new validators tires to register, but the limit for this interval is already reached - * @dev Signature "0xd9873182" + * @notice Thrown when the Node operator tries to withdraw amount bigger than the minimum amount + * @dev Signature "0x2bcee45d" */ - error ValidatorLimitPerIntervalReached(); + error InvalidValidatorTicketAmount(uint256 amount, uint256 minimumVTAmount); /** * @notice Thrown when the new validators tires to register to a module, but the limit for this module is already reached @@ -90,12 +86,6 @@ interface IPufferProtocol is IPufferProtocolStorage { */ error InvalidPufferModule(); - /** - * @notice Thrown if Guardians try to re-submit the backing data - * @dev Signature "0xf93417f7" - */ - error OutsideUpdateWindow(); - /** * @notice Emitted when the new Puffer module is created * @dev Signature "0xd95c47914545148df84d115c3a83350c2b0044a8efa7dbe2cff795a70fe129a1" @@ -108,24 +98,18 @@ interface IPufferProtocol is IPufferProtocolStorage { */ event ModuleChanged(bytes32 indexed moduleName, address oldModule, address newModule); - /** - * @notice Emitted when the Guardians fee rate is changed from `oldRate` to `newRate` - * @dev Signature "0xdc450026d966b67c62d26cf532d9a568be6c73c01251576c5d6a71bb19463d2f" - */ - event GuardiansFeeRateChanged(uint256 oldRate, uint256 newRate); - - /** - * @notice Emitted when the Withdrawal Pool rate is changed from `oldRate` to `newRate` - * @dev Signature "0x7b574a9dff23e9e2774a4ee52a42ad285a36eb8dd120eeebc5568d3b02f0683c" - */ - event WithdrawalPoolRateChanged(uint256 oldRate, uint256 newRate); - /** * @notice Emitted when the module's validator limit is changed from `oldLimit` to `newLimit` * @dev Signature "0x21e92cbdc47ef718b9c77ea6a6ee50ff4dd6362ee22041ab77a46dacb93f5355" */ event ValidatorLimitPerModuleChanged(uint256 oldLimit, uint256 newLimit); + /** + * @notice Emitted when the minimum number of days for ValidatorTickets is changed from `oldMinimumNumberOfDays` to `newMinimumNumberOfDays` + * @dev Signature "0xc6f97db308054b44394df54aa17699adff6b9996e9cffb4dcbcb127e20b68abc" + */ + event MinimumVTAmountChanged(uint256 oldMinimumNumberOfDays, uint256 newMinimumNumberOfDays); + /** * @notice Emitted when the ETH `amount` in wei is transferred to `to` address * @dev Signature "0xba7bb5aa419c34d8776b86cc0e9d41e72d74a893a511f361a11af6c05e920c3d" @@ -133,10 +117,16 @@ interface IPufferProtocol is IPufferProtocolStorage { event TransferredETH(address indexed to, uint256 amount); /** - * @notice Emitted when the smoothing commitment is paid - * @dev Signature "0x84e6610d0de4b996419eca9cf06b11fc13c256051f73673c802822674928fb9a" + * @notice Emitted when VT is deposited to the protocol + * @dev Signature "0xd47eb90c0b945baf5f3ae3f1384a7a524a6f78f1461b354c4a09c4001a5cee9c" + */ + event ValidatorTicketsDeposited(address indexed node, address indexed depositor, uint256 amount); + + /** + * @notice Emitted when VT is withdrawn from the protocol + * @dev Signature "0xdf7e884ecac11650e1285647b057fa733a7bb9f1da100e7a8c22aafe4bdf6f40" */ - event SmoothingCommitmentPaid(bytes indexed pubKey, uint256 amountPaid); + event ValidatorTicketsWithdrawn(address indexed node, address indexed recipient, uint256 amount); /** * @notice Emitted when the guardians decide to skip validator provisioning for `moduleName` @@ -149,38 +139,24 @@ interface IPufferProtocol is IPufferProtocolStorage { */ event FullWithdrawalsRootPosted(uint256 indexed blockNumber, bytes32 root); - /** - * @notice Emitted when the Guardians update state of the protocol - * @param ethAmount is the ETH amount that is not locked in Beacon chain - * @param lockedETH is the locked ETH amount in Beacon chain - * @param pufETHTotalSupply is the total supply of the pufETH - */ - event BackingUpdated(uint256 ethAmount, uint256 lockedETH, uint256 pufETHTotalSupply, uint256 blockNumber); - - /** - * @notice Emitted when the smoothing commitments are changed - * @dev Signature "0xa1c728453af1b7abc9e0f6046d262db82ac81ccb163125d0cf365bae5dc94475" - */ - event CommitmentsChanged(uint256[] oldCommitments, uint256[] newCommitments); - - /** - * @notice Emitted when the protocol fee changes from `oldValue` to `newValue` - * @dev Signature "0xff4822c8e0d70b6faad0b6d31ab91a6a9a16096f3e70328edbb21b483815b7e6" - */ - event ProtocolFeeRateChanged(uint256 oldValue, uint256 newValue); - - /** - * @notice Emitted when the validator limit per interval is changed from `oldLimit` to `newLimit` - * @dev Signature "0xd6c37e61a7f770549c535431a7a63b047395ebed26acefc1cab277cbbeb1d8b7" - */ - event ValidatorLimitPerIntervalChanged(uint256 oldLimit, uint256 newLimit); - /** * @notice Emitted when the module weights changes from `oldWeights` to `newWeights` * @dev Signature "0xd4c9924bd67ff5bd900dc6b1e03b839c6ffa35386096b0c2a17c03638fa4ebff" */ event ModuleWeightsChanged(bytes32[] oldWeights, bytes32[] newWeights); + /** + * @notice Emitted whenever VT balance is updated (deposit, withdraw, node provision by the guardians, retrieve bond) + * @dev Signature "0xa2db5b08bfaa7d199c195f5ff7695be9809b4198a03eee3bbda42307ea893b70" + */ + event VTBalanceChanged( + address indexed node, + uint256 oldVTBalance, + uint256 newVTBalance, + uint256 oldVirtualVTBalance, + uint256 newVirtualVTBalance + ); + /** * @notice Emitted when the Validator key is registered * @param pubKey is the validator public key @@ -211,14 +187,6 @@ interface IPufferProtocol is IPufferProtocolStorage { */ event SuccessfullyProvisioned(bytes indexed pubKey, uint256 indexed validatorIndex, bytes32 indexed moduleName); - /** - * @notice Emitted when the Validator key is failed to be provisioned - * @param pubKey is the validator public key - * @param validatorIndex is the internal validator index in Puffer Finance, not to be mistaken with validator index on Beacon Chain - * @dev Signature "0x8570512b93af33936e8fa6bfcd755f2c72c42c90569dc288b2e38e839943f0cd" - */ - event FailedToProvision(bytes indexed pubKey, uint256 validatorIndex); - /** * @notice Emitted when the validator is dequeued by the Node operator * @param pubKey is the public key of the Validator @@ -236,30 +204,40 @@ interface IPufferProtocol is IPufferProtocolStorage { function getValidatorInfo(bytes32 moduleName, uint256 validatorIndex) external view returns (Validator memory); /** - * @notice Stops the registration + * @notice Returns the node operator information + * @param node is the node operator address + * @return NodeInfo struct + */ + function getNodeInfo(address node) external view returns (NodeInfo memory); + + /** + * @notice Deposits Validator Tickets for the `node` + */ + function depositValidatorTickets(Permit calldata permit, address node) external; + + /** + * @notice Withdraws the `amount` of Validator Tickers from the `msg.sender` to the `recipient` + * @dev Each active validator requires node operator to have at least `minimumVtAmount` locked + * Can not withdraw virtual VTs + */ + function withdrawValidatorTickets(uint96 amount, address recipient) external; + + /** + * @notice Cancels the Validator registration * @param moduleName is the staking Module * @param validatorIndex is the Index of the validator in Puffer, not to be mistaken with Validator index on beacon chain * @dev Can only be called by the Node Operator, and Validator must be in `Pending` state */ - function stopRegistration(bytes32 moduleName, uint256 validatorIndex) external; + function cancelRegistration(bytes32 moduleName, uint256 validatorIndex) external; /** - * @notice Stops the validator + * @notice Submit a valid MerkleProof and get back the Bond deposited if the validator was not slashed * @dev We will burn pufETH from node operator in case of slashing / receiving less than 32 ETH from a full withdrawal - * @param moduleName is the staking Module - * @param validatorIndex is the Index of the validator in Puffer, not to be mistaken with Validator index on beacon chain - * @param withdrawalAmount is the amount of ETH from the full withdrawal - * @param wasSlashed is the amount of pufETH that we are burning from the node operator + * Anybody can trigger a validator exit as long as the proofs submitted are valid + * @param validatorInfo is the information about the stopped validator * @param merkleProof is the Merkle Proof for a withdrawal */ - function stopValidator( - bytes32 moduleName, - uint256 validatorIndex, - uint256 blockNumber, - uint256 withdrawalAmount, - bool wasSlashed, - bytes32[] calldata merkleProof - ) external; + function retrieveBond(StoppedValidatorInfo calldata validatorInfo, bytes32[] calldata merkleProof) external; /** * @notice Skips the next validator for `moduleName` @@ -269,75 +247,27 @@ interface IPufferProtocol is IPufferProtocolStorage { /** * @notice Sets the module weights array to `newModuleWeights` - * @dev Restricted to DAO + * @dev Restricted to the DAO */ function setModuleWeights(bytes32[] calldata newModuleWeights) external; /** * @notice Sets the module limits for `moduleName` to `limit` - * @dev Restricted to DAO + * @dev Restricted to the DAO */ function setValidatorLimitPerModule(bytes32 moduleName, uint128 limit) external; /** - * @notice Sets the protocol fee rate - * @dev 1% equals `1 * FixedPointMathLib.WAD` - * - * Restricted to DAO - */ - function setProtocolFeeRate(uint256 protocolFeeRate) external; - - /** - * @notice Sets the withdrawal pool rate - * @dev 1% equals `1 * FixedPointMathLib.WAD` - * - * Restricted to DAO - */ - function setWithdrawalPoolRate(uint256 newRate) external; - - /** - * @notice Sets guardians fee rate - * @dev 1% equals `1 * FixedPointMathLib.WAD` - * - * Restricted to DAO - */ - function setGuardiansFeeRate(uint256 newRate) external; - - /** - * @notice Sets the validator limit per interval to `newLimit` - * @dev Restricted to DAO - */ - function setValidatorLimitPerInterval(uint256 newLimit) external; - - /** - * @notice Sets the smoothing commitment amounts - * @dev Restricted to DAO - */ - function setSmoothingCommitments(uint256[] calldata smoothingCommitments) external; - - /** - * @notice Updates the proof of reserve by checking the signatures of the guardians - * @param ethAmount The amount of ETH - * @param lockedETH The locked ETH amount on Beacon Chain - * @param pufETHTotalSupply The total supply of pufETH tokens - * @param blockNumber The block number - * @param numberOfActiveValidators The number of all active validators on Beacon Chain - * @param guardianSignatures The guardian signatures + * @notice Changes the `moduleName` with `newModule` + * @dev Restricted to the DAO */ - function proofOfReserve( - uint256 ethAmount, - uint256 lockedETH, - uint256 pufETHTotalSupply, - uint256 blockNumber, - uint256 numberOfActiveValidators, - bytes[] calldata guardianSignatures - ) external; + function changeModule(bytes32 moduleName, IPufferModule newModule) external; /** - * @notice Changes the `moduleName` with `newModule` - * @dev Restricted to DAO + * @notice Changes the minimum number amount of VT that must be locked per validator + * @dev Restricted to the DAO */ - function changeModule(bytes32 moduleName, IPufferModule newModule) external; + function changeMinimumVTAmount(uint256 newMinimumVTAmount) external; /** * @notice Returns the guardian module @@ -345,14 +275,14 @@ interface IPufferProtocol is IPufferProtocolStorage { function GUARDIAN_MODULE() external view returns (IGuardianModule); /** - * @notice Returns the Puffer Pool + * @notice Returns the Validator ticket ERC20 token */ - function POOL() external view returns (IPufferPool); + function VALIDATOR_TICKET() external view returns (ValidatorTicket); /** - * @notice Returns the Withdrawal Pool + * @notice Returns the Puffer Vault */ - function WITHDRAWAL_POOL() external view returns (IWithdrawalPool); + function PUFFER_VAULT() external view returns (PufferVaultMainnet); /** * @notice Returns the Puffer Module Factory @@ -360,9 +290,9 @@ interface IPufferProtocol is IPufferProtocolStorage { function PUFFER_MODULE_FACTORY() external view returns (IPufferModuleFactory); /** - * @notice Returns the protocol fee rate + * @notice Returns the Puffer Oracle */ - function getProtocolFeeRate() external view returns (uint256); + function PUFFER_ORACLE() external view returns (IPufferOracle); /** * @notice Returns the current module weights @@ -381,9 +311,10 @@ interface IPufferProtocol is IPufferProtocolStorage { /** * @notice Provisions the next node that is in line for provisioning if the `guardianEnclaveSignatures` are valid + * @param vtBurnOffset Is the amount in VT tokens that is credited to Node operator for the validating queue time * @dev You can check who is next for provisioning by calling `getNextValidatorToProvision` method */ - function provisionNode(bytes[] calldata guardianEnclaveSignatures) external; + function provisionNode(bytes[] calldata guardianEnclaveSignatures, uint88 vtBurnOffset) external; /** * @notice Returns the deposit_data_root @@ -417,11 +348,6 @@ interface IPufferProtocol is IPufferProtocolStorage { external returns (address); - /** - * @notice Returns the smoothing commitment for a `numberOfMonths` (in wei) - */ - function getSmoothingCommitment(uint256 numberOfMonths) external view returns (uint256); - /** * @notice Registers a new validator key in a `moduleName` queue with a permit * @dev There is a queue per moduleName and it is FIFO @@ -431,32 +357,18 @@ interface IPufferProtocol is IPufferProtocolStorage { * * @param data The validator key data * @param moduleName The name of the module - * @param numberOfMonths The number of months for the registration - * @param permit The permit for the registration + * @param numberOfDays The number of days for the registration + * @param pufETHPermit The permit for the pufETH + * @param vtPermit The permit for the ValidatorTicket */ - function registerValidatorKeyPermit( + function registerValidatorKey( ValidatorKeyData calldata data, bytes32 moduleName, - uint256 numberOfMonths, - Permit calldata permit + uint256 numberOfDays, + Permit calldata pufETHPermit, + Permit calldata vtPermit ) external payable; - /** - * @notice Registers a new validator in a `moduleName` queue - * @dev There is a queue per moduleName and it is FIFO - */ - function registerValidatorKey(ValidatorKeyData calldata data, bytes32 moduleName, uint256 numberOfMonths) - external - payable; - - /** - * @notice Extends the commitment for a validator in a specific module - * @param moduleName The name of the module - * @param validatorIndex The index of the validator in the module - * @param numberOfMonths The number of months to extend the commitment for - */ - function extendCommitment(bytes32 moduleName, uint256 validatorIndex, uint256 numberOfMonths) external payable; - /** * @notice Returns the pending validator index for `moduleName` */ @@ -467,6 +379,11 @@ interface IPufferProtocol is IPufferProtocolStorage { */ function getNextValidatorToBeProvisionedIndex(bytes32 moduleName) external view returns (uint256); + /** + * @notice Returns the amount of Validator Tickets in the PufferProtocol for the `owner` + */ + function getValidatorTicketsBalance(address owner) external returns (uint256); + /** * @notice Returns the next in line for provisioning * @dev The order in which the modules are selected is based on Module Weights @@ -474,18 +391,13 @@ interface IPufferProtocol is IPufferProtocolStorage { */ function getNextValidatorToProvision() external view returns (bytes32 moduleName, uint256 indexToBeProvisioned); - /** - * @notice Returns the validator limit per interval - */ - function getValidatorLimitPerInterval() external view returns (uint256); - /** * @notice Returns the withdrawal credentials for a `module` */ function getWithdrawalCredentials(address module) external view returns (bytes memory); /** - * @notice Returns the treasury address + * @notice Returns the minimum amount of Validator Tokens to run a validator */ - function TREASURY() external view returns (address payable); + function getMinimumVtAmount() external view returns (uint256); } diff --git a/src/interface/IPufferProtocolStorage.sol b/src/interface/IPufferProtocolStorage.sol deleted file mode 100644 index 6b5dbf18..00000000 --- a/src/interface/IPufferProtocolStorage.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { PufferPoolStorage } from "puffer/struct/PufferPoolStorage.sol"; - -interface IPufferProtocolStorage { - /** - * @notice Returns the PufferPool storage - */ - function getPufferPoolStorage() external pure returns (PufferPoolStorage memory); -} diff --git a/src/interface/IWithdrawalPool.sol b/src/interface/IWithdrawalPool.sol deleted file mode 100644 index 3ab1ebba..00000000 --- a/src/interface/IWithdrawalPool.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { Permit } from "puffer/struct/Permit.sol"; - -/** - * @title IWithdrawalPool - * @author Puffer Finance - * @custom:security-contact security@puffer.fi - */ -interface IWithdrawalPool { - /** - * @notice Thrown if the fee rate is not valid - * @dev Signature "0x56d69198" - */ - error InvalidFeeRate(); - - /** - * @notice Emitted when the withdrawal fee is changed - */ - event WithdrawalFeeChanged(uint256 oldRate, uint256 newRate); - - /** - * @notice Sets the withdrawal fee to the specified amount - * @param withdrawalFee The new withdrawal fee to be set - */ - function setWithdrawalFee(uint256 withdrawalFee) external; - - /** - * @notice Burns `pufETHAmount` and sends the ETH to `to` - * @dev You need to approve `pufETHAmount` to this contract by calling pool.approve - * @return ETH Amount redeemed - */ - function withdrawETH(address to, uint256 pufETHAmount) external returns (uint256); - - /** - * @notice Burns pufETH and sends ETH to `to` - * Permit allows a gasless approval. Owner signs a message giving transfer approval to this contract. - * @param permit is the struct required by IERC20Permit-permit - */ - function withdrawETH(address to, Permit calldata permit) external returns (uint256); -} diff --git a/src/struct/NodeInfo.sol b/src/struct/NodeInfo.sol new file mode 100644 index 00000000..ae2f8b71 --- /dev/null +++ b/src/struct/NodeInfo.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @dev Everything is packed in 1 storage slot + */ +struct NodeInfo { + uint16 activeValidatorCount; + uint8 pendingValidatorCount; + uint48 lastUpdate; + uint96 vtBalance; + uint88 virtualVTBalance; +} diff --git a/src/struct/Permit.sol b/src/struct/Permit.sol index ba063da0..a25bb903 100644 --- a/src/struct/Permit.sol +++ b/src/struct/Permit.sol @@ -5,7 +5,6 @@ pragma solidity >=0.8.0 <0.9.0; * @dev Struct representing a permit for a specific action. */ struct Permit { - address owner; uint256 deadline; uint256 amount; uint8 v; diff --git a/src/struct/ProtocolStorage.sol b/src/struct/ProtocolStorage.sol index e521d838..ac34a6d8 100644 --- a/src/struct/ProtocolStorage.sol +++ b/src/struct/ProtocolStorage.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; import { Validator } from "puffer/struct/Validator.sol"; +import { NodeInfo } from "puffer/struct/NodeInfo.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; /** @@ -18,82 +19,58 @@ struct ProtocolStorage { * Slot 0 */ bytes32[] moduleWeights; - /** - * @dev Protocol fee rate, can be updated by governance (1e20 = 100%, 1e18 = 1%) - * Slot 1 - */ - uint72 protocolFeeRate; - /** - * @dev WithdrawalPool rate, can be updated by governance (1e20 = 100%, 1e18 = 1%) - * Slot 1 - */ - uint72 withdrawalPoolRate; - /** - * @dev Guardians fee rate, can be updated by governance (1e20 = 100%, 1e18 = 1%) - * Slot 1 - */ - uint72 guardiansFeeRate; - /** - * @dev Number of validators registered in this interval - * Slot 1 - */ - uint16 numberOfValidatorsRegisteredInThisInterval; - /** - * @dev Number of validators allowed per interval - * Slot 1 - */ - uint16 validatorLimitPerInterval; - /** - * @dev Number of active puffer validators - * Slot 2 - */ - uint128 activePufferValidators; - /** - * @dev Total number of active validators on Beacon Chain - * Slot 2 - */ - uint128 numberOfActiveValidators; /** * @dev Select module index - * Slot 3 + * Slot 1 */ uint256 moduleSelectIndex; /** * @dev Mapping of module name to pending validator index for that module - * Slot 4 + * Slot 2 */ mapping(bytes32 moduleName => uint256 pendingValidatorIndex) pendingValidatorIndices; /** * @dev Mapping of a module name to validator queue - * Slot 5 + * Slot 3 */ mapping(bytes32 moduleName => uint256 nextInLineToBeProvisionedIndex) nextToBeProvisioned; /** * @dev Mapping of Module name => idx => Validator * Index is incrementing starting from 0, not to be mistaken with Beacon Chain Validator Index - * Slot 6 + * Slot 4 */ mapping(bytes32 moduleName => mapping(uint256 index => Validator validator)) validators; /** * @dev Mapping of a blockNumber and Merkle Root for full withdrawals - * Slot 7 + * Slot 5 */ mapping(uint256 blockNumber => bytes32 root) fullWithdrawalsRoots; /** * @dev Mapping between module name and a module - * Slot 8 + * Slot 6 */ mapping(bytes32 moduleName => IPufferModule moduleAddress) modules; /** * @dev Array of smoothing commitments for a number of months and smoothing commitment amount (in wei) - * Slot 9 + * Slot 7 */ uint256[] smoothingCommitments; /** * @dev Mapping of Module name => Module limit - * Slot 10 + * Slot 8 */ mapping(bytes32 moduleName => ModuleLimit moduleLimit) moduleLimits; + /** + * @dev Mapping of Node operator address => Node operator information + * Slot 9 + */ + mapping(address node => NodeInfo info) nodeOperatorInfo; + /** + * @dev Minimum number of VT tokens per validator + * 1 DAY = 1e18 + * Slot 10 + */ + uint256 minimumVtAmount; } struct ModuleLimit { diff --git a/src/struct/PufferPoolStorage.sol b/src/struct/PufferPoolStorage.sol deleted file mode 100644 index f5597973..00000000 --- a/src/struct/PufferPoolStorage.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -/** - * @custom:storage-location erc7201:PufferPoolStorage.storage - */ -struct PufferPoolStorage { - /** - * @dev Unlocked ETH amount - * Slot 0 - */ - uint256 ethAmount; - /** - * @dev Locked ETH amount in Beacon Chain - * Slot 1 - */ - uint256 lockedETH; - /** - * @dev pufETH total token supply - * Slot 2 - */ - uint256 pufETHTotalSupply; - /** - * @dev Block number for when the values were updated - * Slot 3 - */ - uint256 lastUpdate; -} diff --git a/src/struct/StoppedValidatorInfo.sol b/src/struct/StoppedValidatorInfo.sol new file mode 100644 index 00000000..efee4206 --- /dev/null +++ b/src/struct/StoppedValidatorInfo.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @dev Stopped validator info + */ +struct StoppedValidatorInfo { + /// @dev Name of the module where the validator was participating. + bytes32 moduleName; + /// @dev Index of the validator in the module's validator list. + uint256 validatorIndex; + /// @dev Block number at which the validator was stopped. + uint256 blockNumber; + /// @dev Amount of funds withdrawn upon validator stoppage. + uint256 withdrawalAmount; + /// @dev Indicates whether the validator was slashed before stopping. + bool wasSlashed; + /// @dev Timestamp when the validator was stopped. + uint256 validatorStopTimestamp; +} diff --git a/src/struct/Validator.sol b/src/struct/Validator.sol index f4549875..5b19990d 100644 --- a/src/struct/Validator.sol +++ b/src/struct/Validator.sol @@ -5,11 +5,11 @@ import { Status } from "puffer/struct/Status.sol"; /** * @dev Validator struct + * @todo optimize struct */ struct Validator { address node; // Address of the Node operator address module; // In which module is the Validator participating - uint24 monthsCommitted; // Number of months uint64 bond; // Validator bond (in pufETH) Status status; // Validator status bytes pubKey; // Validator public key diff --git a/test/fork-tests/EigenPodRewards.integration.t.sol b/test/fork-tests/EigenPodRewards.integration.t.sol index b5a834c0..30f542fe 100644 --- a/test/fork-tests/EigenPodRewards.integration.t.sol +++ b/test/fork-tests/EigenPodRewards.integration.t.sol @@ -3,11 +3,10 @@ pragma solidity >=0.8.0 <0.9.0; import { IntegrationTestHelper } from "../helpers/IntegrationTestHelper.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; -import { PufferDeployment } from "script/DeploymentStructs.sol"; +import { PufferProtocolDeployment } from "script/DeploymentStructs.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModule } from "puffer/PufferModule.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; -import { PufferDeployment } from "script/DeploymentStructs.sol"; contract EigenPodRewards is IntegrationTestHelper { function setUp() public { diff --git a/test/fork-tests/PufferPool.integration.t.sol b/test/fork-tests/PufferPool.integration.t.sol index fafdde31..95f1b3d7 100644 --- a/test/fork-tests/PufferPool.integration.t.sol +++ b/test/fork-tests/PufferPool.integration.t.sol @@ -98,6 +98,7 @@ contract PufferPoolIntegrationTest is IntegrationTestHelper { function _getSignerSignatureForDelegation(uint256 stakerSK, uint256 expiry) internal + view returns (IDelegationManager.SignatureWithExpiry memory) { IDelegationManager.SignatureWithExpiry memory stakerSignatureAndExpiry; @@ -126,7 +127,7 @@ contract PufferPoolIntegrationTest is IntegrationTestHelper { return stakerSignatureAndExpiry; } - function _getSignature(uint256 stakerSK, uint256 amount, uint256 expiry) internal returns (bytes memory) { + function _getSignature(uint256 stakerSK, uint256 amount, uint256 expiry) internal view returns (bytes memory) { // uint256 nonceBefore = 0; // uint256 nonceBefore = MissingInInterface(address(eigenStrategyManager)).nonces(vm.addr(stakerSK)); // how to get real nonce uint256 nonceBefore = 0; diff --git a/test/handlers/PufferProtocolHandler.sol b/test/handlers/PufferProtocolHandler.sol index a8051158..5cb203e4 100644 --- a/test/handlers/PufferProtocolHandler.sol +++ b/test/handlers/PufferProtocolHandler.sol @@ -1,739 +1,737 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { PufferPool } from "puffer/PufferPool.sol"; -import { IPufferModule } from "puffer/interface/IPufferModule.sol"; -import { IPufferProtocol } from "puffer/interface/IPufferProtocol.sol"; -import { WithdrawalPool } from "puffer/WithdrawalPool.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; -import { EnumerableMap } from "openzeppelin/utils/structs/EnumerableMap.sol"; -import { EnumerableSet } from "openzeppelin/utils/structs/EnumerableSet.sol"; -import { RaveEvidence } from "puffer/struct/RaveEvidence.sol"; -import { console } from "forge-std/console.sol"; -import { Test } from "forge-std/Test.sol"; -import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { ValidatorKeyData } from "puffer/struct/ValidatorKeyData.sol"; -import { Validator } from "puffer/struct/Validator.sol"; -import { Status } from "puffer/struct/Status.sol"; -import { TestHelper } from "../helpers/TestHelper.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { LibGuardianMessages } from "puffer/LibGuardianMessages.sol"; -import { Merkle } from "murky/Merkle.sol"; -import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; -import { ROLE_ID_PUFFER_PROTOCOL } from "script/SetupAccess.s.sol"; - -struct ProvisionedValidator { - bytes32 moduleName; - uint256 idx; -} - -contract PufferProtocolHandler is Test { - using EnumerableMap for EnumerableMap.AddressToUintMap; - using EnumerableSet for EnumerableSet.AddressSet; - using SafeTransferLib for address; - using SafeTransferLib for address payable; - - uint256 guardian1SKEnclave = 81165043675487275545095207072241430673874640255053335052777448899322561824201; - address guardian1Enclave = vm.addr(guardian1SKEnclave); - uint256 guardian2SKEnclave = 90480947395980135991870782913815514305328820213706480966227475230529794843518; - address guardian2Enclave = vm.addr(guardian2SKEnclave); - uint256 guardian3SKEnclave = 56094429399408807348734910221877888701411489680816282162734349635927251229227; - TestHelper testhelper; - - address[] public actors; - - address DAO = makeAddr("DAO"); - - uint256[] guardiansEnclavePks; - PufferPool pool; - IWithdrawalPool withdrawalPool; - PufferProtocol pufferProtocol; - - EnumerableMap.AddressToUintMap _pufETHDepositors; - - EnumerableSet.AddressSet _nodeOperators; - - struct Data { - address owner; - bytes32 pubKeyPart; - } - - uint256 public ghost_eth_deposited_amount; - uint256 public ghost_locked_amount; - uint256 public ghost_eth_rewards_amount; - uint256 public ghost_block_number = 10000; - uint256 public ghost_validators = 0; - uint256 public ghost_pufETH_bond_amount = 0; // bond amount that should be in puffer protocol - ProvisionedValidator[] public ghost_validators_validating; - - address _accessManagerAdmin; - - // Previous ETH balance of PufferPool - uint256 public previousBalance; - - // This is important because that is the only way that ETH is leaving PufferPool - bool public ethLeavingThePool; - - // Counter for the calls in the invariant test - mapping(bytes32 => uint256) public calls; - uint256 totalCalls; - - struct ProvisioningData { - Status status; - bytes32 pubKeypart; - } - - mapping(bytes32 queue => ProvisioningData[] validators) _validatorQueue; - mapping(bytes32 queue => uint256 nextForProvisioning) ghost_nextForProvisioning; - - address internal currentActor; - - constructor( - TestHelper helper, - PufferPool _pool, - IWithdrawalPool _withdrawalPool, - PufferProtocol protocol, - uint256[] memory _guardiansEnclavePks, - address accessManagerAdmin - ) { - // Initialize actors, skip precompiles - for (uint256 i = 11; i < 1000; ++i) { - address actor = address(uint160(i)); - if (actor.code.length != 0) { - continue; - } - vm.deal(actor, 1000 ether); - - actors.push(actor); - } - - testhelper = helper; - pufferProtocol = protocol; - pool = _pool; - withdrawalPool = _withdrawalPool; - guardiansEnclavePks.push(_guardiansEnclavePks[0]); - guardiansEnclavePks.push(_guardiansEnclavePks[1]); - guardiansEnclavePks.push(_guardiansEnclavePks[2]); - _accessManagerAdmin = accessManagerAdmin; - _enableCall(pufferProtocol.getModuleAddress(bytes32("NO_RESTAKING"))); - - vm.deal(address(this), 20000 ether); - - uint256 initialDepositAmount = 20000 ether; - // bootstrap pool with some eth, assume this will never be liquidated - pool.depositETH{ value: initialDepositAmount }(); - - ghost_eth_deposited_amount += initialDepositAmount; - } - - modifier useActor(uint256 actorIndexSeed) { - currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)]; - vm.startPrank(currentActor); - _; - vm.stopPrank(); - } - - // https://github.com/foundry-rs/foundry/issues/5795 - modifier setCorrectBlockNumber() { - vm.roll(ghost_block_number); - _; - } - - modifier recordPreviousBalance() { - previousBalance = address(pool).balance; - _; - } - - modifier isETHLeavingThePool() { - if (msg.sig == this.provisionNode.selector) { - ethLeavingThePool = true; - } else { - ethLeavingThePool = false; - } - _; - } - - modifier countCall(bytes32 key) { - calls[key]++; - totalCalls++; - _; - } - - // Simulates pool getting ETH as a reward / donation - function depositStakingRewards(uint256 stakingRewardsAmount) - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("depositStakingRewards") - { - // bound the result between min deposit amount and uint64.max value ~18.44 ETH - stakingRewardsAmount = bound(stakingRewardsAmount, 1, uint256(type(uint64).max)); - - vm.deal(address(this), stakingRewardsAmount); - vm.startPrank(address(this)); - address(pool).safeTransferETH(stakingRewardsAmount); - vm.stopPrank(); - - ghost_eth_rewards_amount += stakingRewardsAmount; - } - - // Posts proof of reserve - function proofOfReserve() - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("proofOfReserve") - { - uint256 blockNumber = block.number; - uint256 activeValidators = 20000; - // advance block to where it can be updated next - uint256 nextUpdate = block.number + 7149; // Update interval is 7141 `_UPDATE_INTERVAL` on pufferProtocol - ghost_block_number = nextUpdate; - vm.roll(nextUpdate); - - uint256 pufETHSupply = pool.totalSupply(); - - // At the moment there is no ETH landing in our modules, instead we simulate the deposit to pufferPool using `depositStakingRewards` - uint256 ethAmount = address(pool).balance + address(withdrawalPool).balance + ghost_eth_rewards_amount; - uint256 lockedETH = ghost_locked_amount; - - bytes32 signedMessageHash = LibGuardianMessages._getProofOfReserveMessage( - ethAmount, lockedETH, pufETHSupply, blockNumber, activeValidators - ); - - pufferProtocol.proofOfReserve({ - ethAmount: ethAmount, - lockedETH: lockedETH, - pufETHTotalSupply: pufETHSupply, - blockNumber: blockNumber, - numberOfActiveValidators: activeValidators, - guardianSignatures: _getGuardianEOASignatures(signedMessageHash) - }); - vm.stopPrank(); - } - - // User deposits ETH to get pufETH - function depositETH(uint256 depositorSeed, uint256 amount) - public - useActor(depositorSeed) - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("depositETH") - { - // bound the result between min deposit amount and uint64.max value ~18.44 ETH - amount = bound(amount, 0.01 ether, uint256(type(uint64).max)); - vm.deal(currentActor, amount); - - uint256 expectedPufETHAmount = pool.calculateETHToPufETHAmount(amount); - - uint256 prevBalance = pool.balanceOf(currentActor); - - uint256 pufETHAmount = pool.depositETH{ value: amount }(); - - uint256 afterBalance = pool.balanceOf(currentActor); - - ghost_eth_deposited_amount += amount; - - require(expectedPufETHAmount == afterBalance - prevBalance, "pufETH calculation is wrong"); - require(pufETHAmount == expectedPufETHAmount, "amounts dont match"); - - // Store the depositor and amount of pufETH - (, uint256 prevAmount) = _pufETHDepositors.tryGet(currentActor); - _pufETHDepositors.set(currentActor, prevAmount + expectedPufETHAmount); - } - - // withdraw pufETH for ETH - function withdrawETH(uint256 withdrawerSeed) - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("withdrawETH") - { - // If there are no pufETH holders, deposit ETH - if (_pufETHDepositors.length() == 0) { - return; - } - - uint256 withdrawerIndex = withdrawerSeed % _pufETHDepositors.length(); - - (address withdrawer, uint256 amount) = _pufETHDepositors.at(withdrawerIndex); - - console.log("Withdrawer pufETH amount", amount); - - // Due to limited liquidity in WithdrawalPool, we are withdrawing 1/3 of the user's balance at a time - uint256 burnAmount = amount / 3; - _pufETHDepositors.set(withdrawer, (amount - burnAmount)); - - vm.deal(address(withdrawalPool), 1000000 ether); - console.log("WITHDRAWAL POOL BALANCE:", address(withdrawalPool).balance); - - vm.startPrank(withdrawer); - pool.approve(address(withdrawalPool), type(uint256).max); - withdrawalPool.withdrawETH(withdrawer, burnAmount); - vm.stopPrank(); - } - - // We have three of these to get better call distribution in the invariant tests - function registerValidatorKey3(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) - public - setCorrectBlockNumber - useActor(nodeOperatorSeed) - recordPreviousBalance - isETHLeavingThePool - countCall("registerValidatorKey") - { - _registerValidatorKey(pubKeyPart, moduleSelectorSeed); - } - - function registerValidatorKey2(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) - public - setCorrectBlockNumber - useActor(nodeOperatorSeed) - recordPreviousBalance - isETHLeavingThePool - countCall("registerValidatorKey") - { - _registerValidatorKey(pubKeyPart, moduleSelectorSeed); - } - - // Registers Validator key - function registerValidatorKey(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) - public - setCorrectBlockNumber - useActor(nodeOperatorSeed) - recordPreviousBalance - isETHLeavingThePool - countCall("registerValidatorKey") - { - _registerValidatorKey(pubKeyPart, moduleSelectorSeed); - } - - function postFullWithdrawalsProof(uint256 firstWithdrawalSeed, uint256 secondWithdrawalSeed) - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("postFullWithdrawalsProof") - { - console.log("ghost validators validaitng -->", ghost_validators_validating.length); - // return if there is less than 3 validators - if (ghost_validators_validating.length < 3) { - return; - } - - // Take two active validators and create a withdrawla proof for them - ProvisionedValidator memory first = ghost_validators_validating[0]; - ghost_validators_validating[0] = ghost_validators_validating[ghost_validators_validating.length - 1]; - ghost_validators_validating.pop(); - - ProvisionedValidator memory second = ghost_validators_validating[0]; - ghost_validators_validating[0] = ghost_validators_validating[ghost_validators_validating.length - 1]; - ghost_validators_validating.pop(); - - address[] memory modules = new address[](2); - modules[0] = pufferProtocol.getModuleAddress(first.moduleName); - modules[1] = pufferProtocol.getModuleAddress(second.moduleName); - - // Give funds to modules - vm.deal(modules[0], 200 ether); - vm.deal(modules[1], 100 ether); - - // Amounts of full withdrawals that we want to move from modules to pools - uint256[] memory amounts = new uint256[](2); - amounts[0] = 32 ether; // First one has the rewards - amounts[1] = bound(secondWithdrawalSeed, 31 ether, 32 ether); // Second one doesn't.. - - bool isSlashed = secondWithdrawalSeed % 2 == 0 ? true : false; - - // Build merkle root and get two proofs - - (bytes32 merkleRoot, bytes32[] memory proof1, bytes32[] memory proof2) = - _buildMerkle(first, amounts[0], second, amounts[1], isSlashed); - - uint256 blockNumber = block.number - 5; - - bytes[] memory signatures = _getGuardianEOASignatures( - LibGuardianMessages._getPostFullWithdrawalsRootMessage(merkleRoot, blockNumber, modules, amounts) - ); - - pufferProtocol.postFullWithdrawalsRoot({ - root: merkleRoot, - blockNumber: blockNumber, - modules: modules, - amounts: amounts, - guardianSignatures: signatures - }); - - Validator memory info1 = pufferProtocol.getValidatorInfo(first.moduleName, first.idx); - - uint256 pufETHBalanceBefore = pool.balanceOf(info1.node); - - // Claim proof 1 - pufferProtocol.stopValidator({ - moduleName: first.moduleName, - validatorIndex: first.idx, - blockNumber: blockNumber, - withdrawalAmount: amounts[0], - wasSlashed: false, - merkleProof: proof1 - }); - - // Update ghost locked amount - ghost_locked_amount -= amounts[0]; - ghost_pufETH_bond_amount -= info1.bond; - ghost_validators -= 1; - - uint256 pufETHBalanceAfter = pool.balanceOf(info1.node); - - assertEq(pufETHBalanceAfter, pufETHBalanceBefore + info1.bond, "balance after the stop for first withdrawal"); - - Validator memory info2 = pufferProtocol.getValidatorInfo(second.moduleName, second.idx); - - pufETHBalanceBefore = pool.balanceOf(info2.node); - - // Claim proof 2 - pufferProtocol.stopValidator({ - moduleName: second.moduleName, - validatorIndex: second.idx, - blockNumber: blockNumber, - withdrawalAmount: amounts[1], - wasSlashed: isSlashed, - merkleProof: proof2 - }); - - // Update ghost locked amount - ghost_locked_amount -= amounts[1]; - ghost_pufETH_bond_amount -= info2.bond; - ghost_validators -= 1; - - pufETHBalanceAfter = pool.balanceOf(info2.node); - - uint256 expectedOut = isSlashed - ? pufETHBalanceBefore - : (pufETHBalanceBefore + info2.bond - pool.calculateETHToPufETHAmount(32 ether - amounts[1])); - - assertEq(pufETHBalanceAfter, expectedOut, "balance after the stop for second withdrawal"); - } - - function _registerValidatorKey(bytes32 pubKeyPart, uint256 moduleSelectorSeed) internal { - bytes32[] memory moduleWeights = pufferProtocol.getModuleWeights(); - uint256 moduleIndex = moduleSelectorSeed % moduleWeights.length; +// import { IPufferModule } from "puffer/interface/IPufferModule.sol"; +// import { IPufferProtocol } from "puffer/interface/IPufferProtocol.sol"; +// import { EnumerableMap } from "openzeppelin/utils/structs/EnumerableMap.sol"; +// import { EnumerableSet } from "openzeppelin/utils/structs/EnumerableSet.sol"; +// import { RaveEvidence } from "puffer/struct/RaveEvidence.sol"; +// import { console } from "forge-std/console.sol"; +// import { Test } from "forge-std/Test.sol"; +// import { PufferProtocol } from "puffer/PufferProtocol.sol"; +// import { ValidatorKeyData } from "puffer/struct/ValidatorKeyData.sol"; +// import { Validator } from "puffer/struct/Validator.sol"; +// import { Status } from "puffer/struct/Status.sol"; +// import { TestHelper } from "../helpers/TestHelper.sol"; +// import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +// import { LibGuardianMessages } from "puffer/LibGuardianMessages.sol"; +// import { Permit } from "puffer/struct/Permit.sol"; +// import { Merkle } from "murky/Merkle.sol"; +// import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +// import { ROLE_ID_PUFFER_PROTOCOL } from "script/SetupAccess.s.sol"; + +// struct ProvisionedValidator { +// bytes32 moduleName; +// uint256 idx; +// } + +// contract PufferProtocolHandler is Test { +// using EnumerableMap for EnumerableMap.AddressToUintMap; +// using EnumerableSet for EnumerableSet.AddressSet; +// using SafeTransferLib for address; +// using SafeTransferLib for address payable; + +// uint256 guardian1SKEnclave = 81165043675487275545095207072241430673874640255053335052777448899322561824201; +// address guardian1Enclave = vm.addr(guardian1SKEnclave); +// uint256 guardian2SKEnclave = 90480947395980135991870782913815514305328820213706480966227475230529794843518; +// address guardian2Enclave = vm.addr(guardian2SKEnclave); +// uint256 guardian3SKEnclave = 56094429399408807348734910221877888701411489680816282162734349635927251229227; +// TestHelper testhelper; + +// address[] public actors; + +// address DAO = makeAddr("DAO"); + +// uint256[] guardiansEnclavePks; +// PufferProtocol pufferProtocol; + +// EnumerableMap.AddressToUintMap _pufETHDepositors; + +// EnumerableSet.AddressSet _nodeOperators; + +// Permit emptyPermit; + +// struct Data { +// address owner; +// bytes32 pubKeyPart; +// } + +// uint256 public ghost_eth_deposited_amount; +// uint256 public ghost_locked_amount; +// uint256 public ghost_eth_rewards_amount; +// uint256 public ghost_block_number = 10000; +// uint256 public ghost_validators = 0; +// uint256 public ghost_pufETH_bond_amount = 0; // bond amount that should be in puffer protocol +// ProvisionedValidator[] public ghost_validators_validating; + +// address _accessManagerAdmin; + +// // Previous ETH balance of the Vault +// uint256 public previousBalance; + +// // This is important because that is the only way that ETH is leaving the Vault +// bool public ethLeavingThePool; + +// // Counter for the calls in the invariant test +// mapping(bytes32 => uint256) public calls; +// uint256 totalCalls; + +// struct ProvisioningData { +// Status status; +// bytes32 pubKeypart; +// } + +// mapping(bytes32 queue => ProvisioningData[] validators) _validatorQueue; +// mapping(bytes32 queue => uint256 nextForProvisioning) ghost_nextForProvisioning; + +// address internal currentActor; + +// constructor( +// TestHelper helper, +// PufferProtocol protocol, +// uint256[] memory _guardiansEnclavePks, +// address accessManagerAdmin +// ) { +// // Initialize actors, skip precompiles +// for (uint256 i = 11; i < 1000; ++i) { +// address actor = address(uint160(i)); +// if (actor.code.length != 0) { +// continue; +// } +// vm.deal(actor, 1000 ether); + +// actors.push(actor); +// } + +// testhelper = helper; +// pufferProtocol = protocol; +// guardiansEnclavePks.push(_guardiansEnclavePks[0]); +// guardiansEnclavePks.push(_guardiansEnclavePks[1]); +// guardiansEnclavePks.push(_guardiansEnclavePks[2]); +// _accessManagerAdmin = accessManagerAdmin; +// _enableCall(pufferProtocol.getModuleAddress(bytes32("NO_RESTAKING"))); + +// vm.deal(address(this), 20000 ether); + +// uint256 initialDepositAmount = 20000 ether; +// // bootstrap pool with some eth, assume this will never be liquidated +// pool.depositETH{ value: initialDepositAmount }(); + +// ghost_eth_deposited_amount += initialDepositAmount; +// } + +// modifier useActor(uint256 actorIndexSeed) { +// currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)]; +// vm.startPrank(currentActor); +// _; +// vm.stopPrank(); +// } + +// // https://github.com/foundry-rs/foundry/issues/5795 +// modifier setCorrectBlockNumber() { +// vm.roll(ghost_block_number); +// _; +// } + +// modifier recordPreviousBalance() { +// previousBalance = address(pool).balance; +// _; +// } + +// modifier isETHLeavingThePool() { +// if (msg.sig == this.provisionNode.selector) { +// ethLeavingThePool = true; +// } else { +// ethLeavingThePool = false; +// } +// _; +// } + +// modifier countCall(bytes32 key) { +// calls[key]++; +// totalCalls++; +// _; +// } + +// // Simulates pool getting ETH as a reward / donation +// function depositStakingRewards(uint256 stakingRewardsAmount) +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("depositStakingRewards") +// { +// // bound the result between min deposit amount and uint64.max value ~18.44 ETH +// stakingRewardsAmount = bound(stakingRewardsAmount, 1, uint256(type(uint64).max)); + +// vm.deal(address(this), stakingRewardsAmount); +// vm.startPrank(address(this)); +// address(pool).safeTransferETH(stakingRewardsAmount); +// vm.stopPrank(); + +// ghost_eth_rewards_amount += stakingRewardsAmount; +// } + +// // Posts proof of reserve +// function proofOfReserve() +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("proofOfReserve") +// { +// uint256 blockNumber = block.number; +// uint256 activeValidators = 20000; +// // advance block to where it can be updated next +// uint256 nextUpdate = block.number + 7149; // Update interval is 7141 `_UPDATE_INTERVAL` on pufferProtocol +// ghost_block_number = nextUpdate; +// vm.roll(nextUpdate); + +// uint256 pufETHSupply = pool.totalSupply(); + +// // At the moment there is no ETH landing in our modules, instead we simulate the deposit to pufferPool using `depositStakingRewards` +// uint256 ethAmount = address(pool).balance + address(withdrawalPool).balance + ghost_eth_rewards_amount; +// uint256 lockedETH = ghost_locked_amount; + +// bytes32 signedMessageHash = LibGuardianMessages._getProofOfReserveMessage( +// ethAmount, lockedETH, pufETHSupply, blockNumber, activeValidators +// ); + +// pufferProtocol.proofOfReserve({ +// ethAmount: ethAmount, +// lockedETH: lockedETH, +// pufETHTotalSupply: pufETHSupply, +// blockNumber: blockNumber, +// numberOfActiveValidators: activeValidators, +// guardianSignatures: _getGuardianEOASignatures(signedMessageHash) +// }); +// vm.stopPrank(); +// } + +// // User deposits ETH to get pufETH +// function depositETH(uint256 depositorSeed, uint256 amount) +// public +// useActor(depositorSeed) +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("depositETH") +// { +// // bound the result between min deposit amount and uint64.max value ~18.44 ETH +// amount = bound(amount, 0.01 ether, uint256(type(uint64).max)); +// vm.deal(currentActor, amount); + +// uint256 expectedPufETHAmount = pool.calculateETHToPufETHAmount(amount); + +// uint256 prevBalance = pool.balanceOf(currentActor); + +// uint256 pufETHAmount = pool.depositETH{ value: amount }(); + +// uint256 afterBalance = pool.balanceOf(currentActor); + +// ghost_eth_deposited_amount += amount; + +// require(expectedPufETHAmount == afterBalance - prevBalance, "pufETH calculation is wrong"); +// require(pufETHAmount == expectedPufETHAmount, "amounts dont match"); + +// // Store the depositor and amount of pufETH +// (, uint256 prevAmount) = _pufETHDepositors.tryGet(currentActor); +// _pufETHDepositors.set(currentActor, prevAmount + expectedPufETHAmount); +// } + +// // withdraw pufETH for ETH +// function withdrawETH(uint256 withdrawerSeed) +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("withdrawETH") +// { +// // If there are no pufETH holders, deposit ETH +// if (_pufETHDepositors.length() == 0) { +// return; +// } + +// uint256 withdrawerIndex = withdrawerSeed % _pufETHDepositors.length(); + +// (address withdrawer, uint256 amount) = _pufETHDepositors.at(withdrawerIndex); + +// console.log("Withdrawer pufETH amount", amount); + +// // Due to limited liquidity in WithdrawalPool, we are withdrawing 1/3 of the user's balance at a time +// uint256 burnAmount = amount / 3; +// _pufETHDepositors.set(withdrawer, (amount - burnAmount)); + +// vm.deal(address(withdrawalPool), 1000000 ether); +// console.log("WITHDRAWAL POOL BALANCE:", address(withdrawalPool).balance); + +// vm.startPrank(withdrawer); +// pool.approve(address(withdrawalPool), type(uint256).max); +// withdrawalPool.withdrawETH(withdrawer, burnAmount); +// vm.stopPrank(); +// } + +// // We have three of these to get better call distribution in the invariant tests +// function registerValidatorKey3(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) +// public +// setCorrectBlockNumber +// useActor(nodeOperatorSeed) +// recordPreviousBalance +// isETHLeavingThePool +// countCall("registerValidatorKey") +// { +// _registerValidatorKey(pubKeyPart, moduleSelectorSeed); +// } + +// function registerValidatorKey2(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) +// public +// setCorrectBlockNumber +// useActor(nodeOperatorSeed) +// recordPreviousBalance +// isETHLeavingThePool +// countCall("registerValidatorKey") +// { +// _registerValidatorKey(pubKeyPart, moduleSelectorSeed); +// } + +// // Registers Validator key +// function registerValidatorKey(uint256 nodeOperatorSeed, bytes32 pubKeyPart, uint256 moduleSelectorSeed) +// public +// setCorrectBlockNumber +// useActor(nodeOperatorSeed) +// recordPreviousBalance +// isETHLeavingThePool +// countCall("registerValidatorKey") +// { +// _registerValidatorKey(pubKeyPart, moduleSelectorSeed); +// } + +// function postFullWithdrawalsProof(uint256 firstWithdrawalSeed, uint256 secondWithdrawalSeed) +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("postFullWithdrawalsProof") +// { +// console.log("ghost validators validaitng -->", ghost_validators_validating.length); +// // return if there is less than 3 validators +// if (ghost_validators_validating.length < 3) { +// return; +// } + +// // Take two active validators and create a withdrawla proof for them +// ProvisionedValidator memory first = ghost_validators_validating[0]; +// ghost_validators_validating[0] = ghost_validators_validating[ghost_validators_validating.length - 1]; +// ghost_validators_validating.pop(); + +// ProvisionedValidator memory second = ghost_validators_validating[0]; +// ghost_validators_validating[0] = ghost_validators_validating[ghost_validators_validating.length - 1]; +// ghost_validators_validating.pop(); + +// address[] memory modules = new address[](2); +// modules[0] = pufferProtocol.getModuleAddress(first.moduleName); +// modules[1] = pufferProtocol.getModuleAddress(second.moduleName); + +// // Give funds to modules +// vm.deal(modules[0], 200 ether); +// vm.deal(modules[1], 100 ether); + +// // Amounts of full withdrawals that we want to move from modules to pools +// uint256[] memory amounts = new uint256[](2); +// amounts[0] = 32 ether; // First one has the rewards +// amounts[1] = bound(secondWithdrawalSeed, 31 ether, 32 ether); // Second one doesn't.. + +// bool isSlashed = secondWithdrawalSeed % 2 == 0 ? true : false; + +// // Build merkle root and get two proofs + +// (bytes32 merkleRoot, bytes32[] memory proof1, bytes32[] memory proof2) = +// _buildMerkle(first, amounts[0], second, amounts[1], isSlashed); + +// uint256 blockNumber = block.number - 5; + +// bytes[] memory signatures = _getGuardianEOASignatures( +// LibGuardianMessages._getPostFullWithdrawalsRootMessage(merkleRoot, blockNumber, modules, amounts) +// ); + +// pufferProtocol.postFullWithdrawalsRoot({ +// root: merkleRoot, +// blockNumber: blockNumber, +// modules: modules, +// amounts: amounts, +// guardianSignatures: signatures +// }); + +// Validator memory info1 = pufferProtocol.getValidatorInfo(first.moduleName, first.idx); + +// uint256 pufETHBalanceBefore = pool.balanceOf(info1.node); + +// // Claim proof 1 +// pufferProtocol.retrieveBond({ +// moduleName: first.moduleName, +// validatorIndex: first.idx, +// blockNumber: blockNumber, +// withdrawalAmount: amounts[0], +// wasSlashed: false, +// merkleProof: proof1 +// }); + +// // Update ghost locked amount +// ghost_locked_amount -= amounts[0]; +// ghost_pufETH_bond_amount -= info1.bond; +// ghost_validators -= 1; + +// uint256 pufETHBalanceAfter = pool.balanceOf(info1.node); + +// assertEq(pufETHBalanceAfter, pufETHBalanceBefore + info1.bond, "balance after the stop for first withdrawal"); + +// Validator memory info2 = pufferProtocol.getValidatorInfo(second.moduleName, second.idx); + +// pufETHBalanceBefore = pool.balanceOf(info2.node); + +// // Claim proof 2 +// pufferProtocol.retrieveBond({ +// moduleName: second.moduleName, +// validatorIndex: second.idx, +// blockNumber: blockNumber, +// withdrawalAmount: amounts[1], +// wasSlashed: isSlashed, +// merkleProof: proof2 +// }); + +// // Update ghost locked amount +// ghost_locked_amount -= amounts[1]; +// ghost_pufETH_bond_amount -= info2.bond; +// ghost_validators -= 1; + +// pufETHBalanceAfter = pool.balanceOf(info2.node); + +// uint256 expectedOut = isSlashed +// ? pufETHBalanceBefore +// : (pufETHBalanceBefore + info2.bond - pool.calculateETHToPufETHAmount(32 ether - amounts[1])); + +// assertEq(pufETHBalanceAfter, expectedOut, "balance after the stop for second withdrawal"); +// } + +// function _registerValidatorKey(bytes32 pubKeyPart, uint256 moduleSelectorSeed) internal { +// bytes32[] memory moduleWeights = pufferProtocol.getModuleWeights(); +// uint256 moduleIndex = moduleSelectorSeed % moduleWeights.length; - bytes32 moduleName = moduleWeights[moduleIndex]; +// bytes32 moduleName = moduleWeights[moduleIndex]; - vm.deal(currentActor, 5 ether); +// vm.deal(currentActor, 5 ether); - pufferProtocol.getPendingValidatorIndex(moduleName); +// pufferProtocol.getPendingValidatorIndex(moduleName); - uint256 depositedETHAmount = _registerValidatorKey(pubKeyPart, moduleName); - - // Store data and push to queue - ProvisioningData memory validator; - validator.status = Status.PENDING; - validator.pubKeypart = pubKeyPart; - - _validatorQueue[moduleName].push(validator); - - vm.stopPrank(); - - // Account for that deposited eth in ghost variable - ghost_eth_deposited_amount += depositedETHAmount; - ghost_validators += 1; - ghost_pufETH_bond_amount += pool.calculateETHToPufETHAmount(1 ether); - - // Add node operator to the set - _nodeOperators.add(currentActor); - } - - // Creates a puffer module and adds it to weights - function createPufferModule(bytes32 moduleName) - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("createPufferModule") - { - vm.startPrank(DAO); - - bytes32[] memory weights = pufferProtocol.getModuleWeights(); - - bytes32[] memory newWeights = new bytes32[](weights.length + 1); - for (uint256 i = 0; i < weights.length; ++i) { - newWeights[i] = weights[i]; - } - - try pufferProtocol.createPufferModule(moduleName, "", address(0)) { - newWeights[weights.length] = moduleName; - pufferProtocol.setModuleWeights(newWeights); - address createdModule = pufferProtocol.getModuleAddress(moduleName); - _enableCall(createdModule); - } catch (bytes memory reason) { } - - vm.stopPrank(); - } - - // Starts the validating process - function provisionNode() - public - setCorrectBlockNumber - recordPreviousBalance - isETHLeavingThePool - countCall("provisionNode") - { - // If we don't have proxies, create and register validator key, then call this function again with the same params - if (_nodeOperators.length() == 0) { - ethLeavingThePool = false; - return; - } - - // If there is nothing to be provisioned, index returned is max uint256 - (, uint256 i) = pufferProtocol.getNextValidatorToProvision(); - if (i == type(uint256).max) { - ethLeavingThePool = false; - return; - } - - uint256 moduleSelectIndex = pufferProtocol.getModuleSelectIndex(); - bytes32[] memory weights = pufferProtocol.getModuleWeights(); - - bytes32 moduleName = weights[moduleSelectIndex % weights.length]; - - uint256 nextIdx = ghost_nextForProvisioning[moduleName]; - - // Nothing to provision - if (_validatorQueue[moduleName].length <= nextIdx) { - ethLeavingThePool = false; - return; - } - - ProvisioningData memory validatorData = _validatorQueue[moduleName][nextIdx]; - - if (validatorData.status == Status.PENDING) { - bytes memory sig = _getPubKey(validatorData.pubKeypart); - - bytes[] memory signatures = _getGuardianSignatures(sig); - pufferProtocol.provisionNode(signatures); - - ghost_validators_validating.push(ProvisionedValidator({ moduleName: moduleName, idx: nextIdx })); - - // Update ghost variables - ghost_locked_amount += 32 ether; - ghost_nextForProvisioning[moduleName]++; - } - } - - // Stops the validator registration process - function stopRegistration(uint256 moduleSelectIndex) - public - setCorrectBlockNumber - isETHLeavingThePool - recordPreviousBalance - countCall("stopRegistration") - { - bytes32[] memory weights = pufferProtocol.getModuleWeights(); - bytes32 moduleName = weights[moduleSelectIndex % weights.length]; - uint256 pendingIdx = pufferProtocol.getPendingValidatorIndex(moduleName); - - if (pendingIdx == 0) { - return; - } - // Set skip index to pending index for that module - uint256 skipIdx = pendingIdx - 1; - - Validator memory info = pufferProtocol.getValidatorInfo(moduleName, skipIdx); - if (info.status == Status.PENDING) { - // Accounting in ghost vars - ghost_pufETH_bond_amount -= info.bond; - ghost_validators -= 1; - - uint256 pufETHBalanceBefore = pool.balanceOf(info.node); - vm.startPrank(info.node); - pufferProtocol.stopRegistration(moduleName, skipIdx); - uint256 pufETHBalanceAfter = pool.balanceOf(info.node); - assertGt(pufETHBalanceAfter, pufETHBalanceBefore); - _validatorQueue[moduleName][skipIdx].status = Status.DEQUEUED; - console.log("=== Stopped the registration ==="); - } - } - - function callSummary() external view { - console.log("Call summary:"); - console.log("-------------------"); - console.log("totalCalls", totalCalls); - console.log("depositStakingRewards", calls["depositStakingRewards"]); - console.log("depositETH", calls["depositETH"]); - console.log("withdrawETH", calls["withdrawETH"]); - console.log("registerValidatorKey", calls["registerValidatorKey"]); - console.log("createPufferModule", calls["createPufferModule"]); - console.log("provisionNode", calls["provisionNode"]); - console.log("proofOfReserve", calls["proofOfReserve"]); - console.log("stopRegistration", calls["stopRegistration"]); - console.log("postFullWithdrawalsProof", calls["postFullWithdrawalsProof"]); - console.log("-------------------"); - } - - function _getMockValidatorKeyData(bytes memory pubKey, bytes32 moduleName) - internal - view - returns (ValidatorKeyData memory) - { - bytes[] memory newSetOfPubKeys = new bytes[](3); - - // we have 3 guardians in TestHelper.sol - newSetOfPubKeys[0] = bytes("key1"); - newSetOfPubKeys[0] = bytes("key2"); - newSetOfPubKeys[0] = bytes("key3"); - - address module = pufferProtocol.getModuleAddress(moduleName); - - bytes memory withdrawalCredentials = pufferProtocol.getWithdrawalCredentials(module); - - bytes memory randomSignature = - hex"8aa088146c8c6ca6d8ad96648f20e791be7c449ce7035a6bd0a136b8c7b7867f730428af8d4a2b69658bfdade185d6110b938d7a59e98d905e922d53432e216dc88c3384157d74200d3f2de51d31737ce19098ff4d4f54f77f0175e23ac98da5"; - - ValidatorKeyData memory validatorData = ValidatorKeyData({ - blsPubKey: pubKey, // key length must be 48 byte - // mock signature copied from some random deposit transaction - signature: randomSignature, - depositDataRoot: pufferProtocol.getDepositDataRoot({ - pubKey: pubKey, - signature: randomSignature, - withdrawalCredentials: withdrawalCredentials - }), - blsEncryptedPrivKeyShares: new bytes[](3), - blsPubKeySet: new bytes(48), - raveEvidence: new bytes(1) // Guardians are checking it off chain - }); - - return validatorData; - } - - function _getPubKey(bytes32 pubKeypart) internal pure returns (bytes memory) { - return bytes.concat(abi.encodePacked(pubKeypart), bytes16("")); - } - - // Copied from PufferProtocol.t.sol - function _registerValidatorKey(bytes32 pubKeyPart, bytes32 moduleName) - internal - returns (uint256 depositedETHAmount) - { - uint256 momths = bound(block.timestamp, 0, 12); - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(momths); - - bytes memory pubKey = _getPubKey(pubKeyPart); - - ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, moduleName); - - uint256 idx = pufferProtocol.getPendingValidatorIndex(moduleName); - - uint256 bond = 1 ether; - - vm.expectEmit(true, true, true, true); - emit IPufferProtocol.ValidatorKeyRegistered(pubKey, idx, moduleName, true); - pufferProtocol.registerValidatorKey{ value: (smoothingCommitment + bond) }(validatorKeyData, moduleName, momths); - - return (smoothingCommitment + bond); - } - - // Copied from PufferProtocol.t.sol - function _getGuardianSignatures(bytes memory pubKey) internal view returns (bytes[] memory) { - (bytes32 moduleName, uint256 pendingIdx) = pufferProtocol.getNextValidatorToProvision(); - Validator memory validator = pufferProtocol.getValidatorInfo(moduleName, pendingIdx); - // If there is no module return empty byte array - if (validator.module == address(0)) { - return new bytes[](0); - } - bytes memory withdrawalCredentials = pufferProtocol.getWithdrawalCredentials(validator.module); - - bytes32 digest = LibGuardianMessages._getBeaconDepositMessageToBeSigned( - pubKey, - validator.signature, - withdrawalCredentials, - pufferProtocol.getDepositDataRoot({ - pubKey: pubKey, - signature: validator.signature, - withdrawalCredentials: withdrawalCredentials - }) - ); - - return _getGuardianEnclaveSignatures(digest); - } - - function _getGuardianEnclaveSignatures(bytes32 digest) internal view returns (bytes[] memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardian1SKEnclave, digest); - bytes memory signature1 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - (v, r, s) = vm.sign(guardian2SKEnclave, digest); - bytes memory signature2 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - (v, r, s) = vm.sign(guardian3SKEnclave, digest); - bytes memory signature3 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - bytes[] memory guardianSignatures = new bytes[](3); - guardianSignatures[0] = signature1; - guardianSignatures[1] = signature2; - guardianSignatures[2] = signature3; - - return guardianSignatures; - } - - function _getGuardianEOASignatures(bytes32 digest) internal returns (bytes[] memory) { - // Create Guardian wallets - (, uint256 guardian1SK) = makeAddrAndKey("guardian1"); - (, uint256 guardian2SK) = makeAddrAndKey("guardian2"); - (, uint256 guardian3SK) = makeAddrAndKey("guardian3"); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardian1SK, digest); - bytes memory signature1 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - (v, r, s) = vm.sign(guardian2SK, digest); - bytes memory signature2 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - (v, r, s) = vm.sign(guardian3SK, digest); - bytes memory signature3 = abi.encodePacked(r, s, v); // note the order here is different from line above. - - bytes[] memory guardianSignatures = new bytes[](3); - guardianSignatures[0] = signature1; - guardianSignatures[1] = signature2; - guardianSignatures[2] = signature3; - - return guardianSignatures; - } - - function _buildMerkle( - ProvisionedValidator memory first, - uint256 firstAmount, - ProvisionedValidator memory second, - uint256 secondAmount, - bool slashed - ) public returns (bytes32, bytes32[] memory, bytes32[] memory) { - // Initialize - Merkle m = new Merkle(); - uint256 wasSlashed = slashed == true ? 1 : 0; - - bytes32[] memory data = new bytes32[](2); - data[0] = keccak256(bytes.concat(keccak256(abi.encode(first.moduleName, first.idx, firstAmount, uint8(0))))); - data[1] = keccak256( - bytes.concat(keccak256(abi.encode(second.moduleName, second.idx, secondAmount, uint8(wasSlashed)))) - ); - - // Get Root, Proof, and Verify - bytes32 root = m.getRoot(data); - bytes32[] memory proof1 = m.getProof(data, 0); - bytes32[] memory proof2 = m.getProof(data, 1); - - return (root, proof1, proof2); - } - - function _enableCall(address module) internal { - // Enable PufferProtocol to call `call` function on module - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = IPufferModule.call.selector; - vm.startPrank(_accessManagerAdmin); - AccessManager(pufferProtocol.authority()).setTargetFunctionRole(module, selectors, ROLE_ID_PUFFER_PROTOCOL); - vm.stopPrank(); - } -} +// uint256 depositedETHAmount = _registerValidatorKey(pubKeyPart, moduleName); + +// // Store data and push to queue +// ProvisioningData memory validator; +// validator.status = Status.PENDING; +// validator.pubKeypart = pubKeyPart; + +// _validatorQueue[moduleName].push(validator); + +// vm.stopPrank(); + +// // Account for that deposited eth in ghost variable +// ghost_eth_deposited_amount += depositedETHAmount; +// ghost_validators += 1; +// ghost_pufETH_bond_amount += pool.calculateETHToPufETHAmount(1 ether); + +// // Add node operator to the set +// _nodeOperators.add(currentActor); +// } + +// // Creates a puffer module and adds it to weights +// function createPufferModule(bytes32 moduleName) +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("createPufferModule") +// { +// vm.startPrank(DAO); + +// bytes32[] memory weights = pufferProtocol.getModuleWeights(); + +// bytes32[] memory newWeights = new bytes32[](weights.length + 1); +// for (uint256 i = 0; i < weights.length; ++i) { +// newWeights[i] = weights[i]; +// } + +// try pufferProtocol.createPufferModule(moduleName, "", address(0)) { +// newWeights[weights.length] = moduleName; +// pufferProtocol.setModuleWeights(newWeights); +// address createdModule = pufferProtocol.getModuleAddress(moduleName); +// _enableCall(createdModule); +// } catch (bytes memory reason) { } + +// vm.stopPrank(); +// } + +// // Starts the validating process +// function provisionNode() +// public +// setCorrectBlockNumber +// recordPreviousBalance +// isETHLeavingThePool +// countCall("provisionNode") +// { +// // If we don't have proxies, create and register validator key, then call this function again with the same params +// if (_nodeOperators.length() == 0) { +// ethLeavingThePool = false; +// return; +// } + +// // If there is nothing to be provisioned, index returned is max uint256 +// (, uint256 i) = pufferProtocol.getNextValidatorToProvision(); +// if (i == type(uint256).max) { +// ethLeavingThePool = false; +// return; +// } + +// uint256 moduleSelectIndex = pufferProtocol.getModuleSelectIndex(); +// bytes32[] memory weights = pufferProtocol.getModuleWeights(); + +// bytes32 moduleName = weights[moduleSelectIndex % weights.length]; + +// uint256 nextIdx = ghost_nextForProvisioning[moduleName]; + +// // Nothing to provision +// if (_validatorQueue[moduleName].length <= nextIdx) { +// ethLeavingThePool = false; +// return; +// } + +// ProvisioningData memory validatorData = _validatorQueue[moduleName][nextIdx]; + +// if (validatorData.status == Status.PENDING) { +// bytes memory sig = _getPubKey(validatorData.pubKeypart); + +// bytes[] memory signatures = _getGuardianSignatures(sig); +// pufferProtocol.provisionNode(signatures); + +// ghost_validators_validating.push(ProvisionedValidator({ moduleName: moduleName, idx: nextIdx })); + +// // Update ghost variables +// ghost_locked_amount += 32 ether; +// ghost_nextForProvisioning[moduleName]++; +// } +// } + +// // Stops the validator registration process +// function stopRegistration(uint256 moduleSelectIndex) +// public +// setCorrectBlockNumber +// isETHLeavingThePool +// recordPreviousBalance +// countCall("stopRegistration") +// { +// bytes32[] memory weights = pufferProtocol.getModuleWeights(); +// bytes32 moduleName = weights[moduleSelectIndex % weights.length]; +// uint256 pendingIdx = pufferProtocol.getPendingValidatorIndex(moduleName); + +// if (pendingIdx == 0) { +// return; +// } +// // Set skip index to pending index for that module +// uint256 skipIdx = pendingIdx - 1; + +// Validator memory info = pufferProtocol.getValidatorInfo(moduleName, skipIdx); +// if (info.status == Status.PENDING) { +// // Accounting in ghost vars +// ghost_pufETH_bond_amount -= info.bond; +// ghost_validators -= 1; + +// uint256 pufETHBalanceBefore = pool.balanceOf(info.node); +// vm.startPrank(info.node); +// pufferProtocol.stopRegistration(moduleName, skipIdx); +// uint256 pufETHBalanceAfter = pool.balanceOf(info.node); +// assertGt(pufETHBalanceAfter, pufETHBalanceBefore); +// _validatorQueue[moduleName][skipIdx].status = Status.DEQUEUED; +// console.log("=== Stopped the registration ==="); +// } +// } + +// function callSummary() external view { +// console.log("Call summary:"); +// console.log("-------------------"); +// console.log("totalCalls", totalCalls); +// console.log("depositStakingRewards", calls["depositStakingRewards"]); +// console.log("depositETH", calls["depositETH"]); +// console.log("withdrawETH", calls["withdrawETH"]); +// console.log("registerValidatorKey", calls["registerValidatorKey"]); +// console.log("createPufferModule", calls["createPufferModule"]); +// console.log("provisionNode", calls["provisionNode"]); +// console.log("proofOfReserve", calls["proofOfReserve"]); +// console.log("stopRegistration", calls["stopRegistration"]); +// console.log("postFullWithdrawalsProof", calls["postFullWithdrawalsProof"]); +// console.log("-------------------"); +// } + +// function _getMockValidatorKeyData(bytes memory pubKey, bytes32 moduleName) +// internal +// view +// returns (ValidatorKeyData memory) +// { +// bytes[] memory newSetOfPubKeys = new bytes[](3); + +// // we have 3 guardians in TestHelper.sol +// newSetOfPubKeys[0] = bytes("key1"); +// newSetOfPubKeys[0] = bytes("key2"); +// newSetOfPubKeys[0] = bytes("key3"); + +// address module = pufferProtocol.getModuleAddress(moduleName); + +// bytes memory withdrawalCredentials = pufferProtocol.getWithdrawalCredentials(module); + +// bytes memory randomSignature = +// hex"8aa088146c8c6ca6d8ad96648f20e791be7c449ce7035a6bd0a136b8c7b7867f730428af8d4a2b69658bfdade185d6110b938d7a59e98d905e922d53432e216dc88c3384157d74200d3f2de51d31737ce19098ff4d4f54f77f0175e23ac98da5"; + +// ValidatorKeyData memory validatorData = ValidatorKeyData({ +// blsPubKey: pubKey, // key length must be 48 byte +// // mock signature copied from some random deposit transaction +// signature: randomSignature, +// depositDataRoot: pufferProtocol.getDepositDataRoot({ +// pubKey: pubKey, +// signature: randomSignature, +// withdrawalCredentials: withdrawalCredentials +// }), +// blsEncryptedPrivKeyShares: new bytes[](3), +// blsPubKeySet: new bytes(48), +// raveEvidence: new bytes(1) // Guardians are checking it off chain +// }); + +// return validatorData; +// } + +// function _getPubKey(bytes32 pubKeypart) internal pure returns (bytes memory) { +// return bytes.concat(abi.encodePacked(pubKeypart), bytes16("")); +// } + +// // Copied from PufferProtocol.t.sol +// function _registerValidatorKey(bytes32 pubKeyPart, bytes32 moduleName) +// internal +// returns (uint256 depositedETHAmount) +// { +// uint256 months = bound(block.timestamp, 0, 12); +// // uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(months); +// uint256 smoothingCommitment = 0.5 ether; + +// bytes memory pubKey = _getPubKey(pubKeyPart); + +// ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, moduleName); + +// uint256 idx = pufferProtocol.getPendingValidatorIndex(moduleName); + +// uint256 bond = 1 ether; + +// vm.expectEmit(true, true, true, true); +// emit IPufferProtocol.ValidatorKeyRegistered(pubKey, idx, moduleName, true); +// pufferProtocol.registerValidatorKey{ value: (smoothingCommitment + bond) }( +// validatorKeyData, moduleName, months, emptyPermit, emptyPermit +// ); + +// return (smoothingCommitment + bond); +// } + +// // Copied from PufferProtocol.t.sol +// function _getGuardianSignatures(bytes memory pubKey) internal view returns (bytes[] memory) { +// (bytes32 moduleName, uint256 pendingIdx) = pufferProtocol.getNextValidatorToProvision(); +// Validator memory validator = pufferProtocol.getValidatorInfo(moduleName, pendingIdx); +// // If there is no module return empty byte array +// if (validator.module == address(0)) { +// return new bytes[](0); +// } +// bytes memory withdrawalCredentials = pufferProtocol.getWithdrawalCredentials(validator.module); + +// bytes32 digest = LibGuardianMessages._getBeaconDepositMessageToBeSigned( +// pendingIdx, +// pubKey, +// validator.signature, +// withdrawalCredentials, +// pufferProtocol.getDepositDataRoot({ +// pubKey: pubKey, +// signature: validator.signature, +// withdrawalCredentials: withdrawalCredentials +// }) +// ); + +// return _getGuardianEnclaveSignatures(digest); +// } + +// function _getGuardianEnclaveSignatures(bytes32 digest) internal view returns (bytes[] memory) { +// (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardian1SKEnclave, digest); +// bytes memory signature1 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// (v, r, s) = vm.sign(guardian2SKEnclave, digest); +// bytes memory signature2 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// (v, r, s) = vm.sign(guardian3SKEnclave, digest); +// bytes memory signature3 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// bytes[] memory guardianSignatures = new bytes[](3); +// guardianSignatures[0] = signature1; +// guardianSignatures[1] = signature2; +// guardianSignatures[2] = signature3; + +// return guardianSignatures; +// } + +// function _getGuardianEOASignatures(bytes32 digest) internal returns (bytes[] memory) { +// // Create Guardian wallets +// (, uint256 guardian1SK) = makeAddrAndKey("guardian1"); +// (, uint256 guardian2SK) = makeAddrAndKey("guardian2"); +// (, uint256 guardian3SK) = makeAddrAndKey("guardian3"); +// (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardian1SK, digest); +// bytes memory signature1 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// (v, r, s) = vm.sign(guardian2SK, digest); +// bytes memory signature2 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// (v, r, s) = vm.sign(guardian3SK, digest); +// bytes memory signature3 = abi.encodePacked(r, s, v); // note the order here is different from line above. + +// bytes[] memory guardianSignatures = new bytes[](3); +// guardianSignatures[0] = signature1; +// guardianSignatures[1] = signature2; +// guardianSignatures[2] = signature3; + +// return guardianSignatures; +// } + +// function _buildMerkle( +// ProvisionedValidator memory first, +// uint256 firstAmount, +// ProvisionedValidator memory second, +// uint256 secondAmount, +// bool slashed +// ) public returns (bytes32, bytes32[] memory, bytes32[] memory) { +// // Initialize +// Merkle m = new Merkle(); +// uint256 wasSlashed = slashed == true ? 1 : 0; + +// bytes32[] memory data = new bytes32[](2); +// data[0] = keccak256(bytes.concat(keccak256(abi.encode(first.moduleName, first.idx, firstAmount, uint8(0))))); +// data[1] = keccak256( +// bytes.concat(keccak256(abi.encode(second.moduleName, second.idx, secondAmount, uint8(wasSlashed)))) +// ); + +// // Get Root, Proof, and Verify +// bytes32 root = m.getRoot(data); +// bytes32[] memory proof1 = m.getProof(data, 0); +// bytes32[] memory proof2 = m.getProof(data, 1); + +// return (root, proof1, proof2); +// } + +// function _enableCall(address module) internal { +// // Enable PufferProtocol to call `call` function on module +// bytes4[] memory selectors = new bytes4[](1); +// selectors[0] = IPufferModule.call.selector; +// vm.startPrank(_accessManagerAdmin); +// AccessManager(pufferProtocol.authority()).setTargetFunctionRole(module, selectors, ROLE_ID_PUFFER_PROTOCOL); +// vm.stopPrank(); +// } +// } diff --git a/test/helpers/IntegrationTestHelper.sol b/test/helpers/IntegrationTestHelper.sol index 735f849f..7cb2c4fd 100644 --- a/test/helpers/IntegrationTestHelper.sol +++ b/test/helpers/IntegrationTestHelper.sol @@ -2,27 +2,18 @@ pragma solidity >=0.8.0 <0.9.0; import "forge-std/Test.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; -import { DeployGuardians } from "script/DeployGuardians.s.sol"; -import { DeployPuffer } from "script/DeployPuffer.s.sol"; -import { PufferDeployment } from "script/DeploymentStructs.sol"; -import { GuardiansDeployment } from "script/DeploymentStructs.sol"; +import { PufferProtocolDeployment } from "script/DeploymentStructs.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModuleFactory } from "puffer/PufferModuleFactory.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; -import { PufferDeployment } from "script/DeploymentStructs.sol"; import { IEnclaveVerifier } from "puffer/interface/IEnclaveVerifier.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; contract IntegrationTestHelper is Test { - PufferPool public pool; PufferProtocol public pufferProtocol; - IWithdrawalPool public withdrawalPool; UpgradeableBeacon public beacon; PufferModuleFactory public moduleFactory; @@ -51,16 +42,12 @@ contract IntegrationTestHelper is Test { function _deployAndLabel(address[] memory guardians, uint256 threshold) internal { // Deploy everything with one script - PufferDeployment memory pufferDeployment = new DeployEverything().run(guardians, threshold); + PufferProtocolDeployment memory pufferDeployment = new DeployEverything().run(guardians, threshold); pufferProtocol = PufferProtocol(payable(pufferDeployment.pufferProtocol)); vm.label(address(pufferProtocol), "PufferProtocol"); accessManager = AccessManager(pufferDeployment.accessManager); vm.label(address(accessManager), "AccessManager"); - pool = PufferPool(payable(pufferDeployment.pufferPool)); - vm.label(address(pool), "PufferPool"); - withdrawalPool = IWithdrawalPool(pufferDeployment.withdrawalPool); - vm.label(address(withdrawalPool), "WithdrawalPool"); verifier = IEnclaveVerifier(pufferDeployment.enclaveVerifier); vm.label(address(verifier), "EnclaveVerifier"); guardianModule = GuardianModule(payable(pufferDeployment.guardianModule)); diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 995e0483..33777d25 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -4,19 +4,26 @@ pragma solidity >=0.8.0 <0.9.0; import "forge-std/Test.sol"; import { BaseScript } from "script/BaseScript.s.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; +import { PufferOracle } from "puffer/PufferOracle.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModuleFactory } from "puffer/PufferModuleFactory.sol"; import { RaveEvidence } from "puffer/struct/RaveEvidence.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; import { IGuardianModule } from "puffer/interface/IGuardianModule.sol"; import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; -import { PufferDeployment } from "script/DeploymentStructs.sol"; +import { PufferProtocolDeployment } from "script/DeploymentStructs.sol"; import { IEnclaveVerifier } from "puffer/interface/IEnclaveVerifier.sol"; import { Guardian1RaveEvidence, Guardian2RaveEvidence, Guardian3RaveEvidence } from "./GuardiansRaveEvidence.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { Permit } from "puffer/struct/Permit.sol"; +import { PufferDepositor } from "pufETH/PufferDepositor.sol"; +import { PufferVault } from "pufETH/PufferVault.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; +import { stETHMock } from "pufETHTest/mocks/stETHMock.sol"; +import { IWETH } from "pufETH/interface/Other/IWETH.sol"; +import { UpgradePuffETH } from "pufETHScript/UpgradePuffETH.s.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import "forge-std/console.sol"; contract TestHelper is Test, BaseScript { bytes32 private constant _PERMIT_TYPEHASH = @@ -42,7 +49,7 @@ contract TestHelper is Test, BaseScript { // Addresses that are supposed to be skipped when fuzzing mapping(address fuzzedAddress => bool isFuzzed) internal fuzzedAddressMapping; - // In our test setup we have 3 guardians and 3 guaridan enclave keys + // In our test setup we have 3 guardians and 3 guardian enclave keys uint256[] public guardiansEnclavePks; address public guardian1; uint256 public guardian1SK; @@ -64,11 +71,16 @@ contract TestHelper is Test, BaseScript { bytes public guardian3EnclavePubKey = hex"04a55b152177219971a93a64aafc2d61baeaf86526963caa260e71efa2b865527e0307d7bda85312dd6ff23bcc88f2bf228da6295239f72c31b686c48b7b69cdfd"; - PufferPool public pool; + PufferDepositor public pufferDepositor; + PufferVaultMainnet public pufferVault; + stETHMock public stETH; + IWETH public weth; + PufferProtocol public pufferProtocol; - IWithdrawalPool public withdrawalPool; UpgradeableBeacon public beacon; PufferModuleFactory public moduleFactory; + ValidatorTicket public validatorTicket; + PufferOracle public pufferOracle; GuardianModule public guardianModule; @@ -77,6 +89,12 @@ contract TestHelper is Test, BaseScript { address public DAO = makeAddr("DAO"); + address LIQUIDITY_PROVIDER = makeAddr("LIQUIDITY_PROVIDER"); + + // We use the same values in DeployPuffETH.s.sol + address public COMMUNITY_MULTISIG = makeAddr("communityMultisig"); + address public OPERATIONS_MULTISIG = makeAddr("operationsMultisig"); + modifier fuzzedAddress(address addr) virtual { vm.assume(fuzzedAddressMapping[addr] == false); _; @@ -101,13 +119,12 @@ contract TestHelper is Test, BaseScript { fuzzedAddressMapping[ADDRESS_CHEATS] = true; fuzzedAddressMapping[ADDRESS_ZERO] = true; fuzzedAddressMapping[ADDRESS_ONE] = true; - fuzzedAddressMapping[address(withdrawalPool)] = true; fuzzedAddressMapping[address(guardianModule)] = true; fuzzedAddressMapping[address(verifier)] = true; fuzzedAddressMapping[address(accessManager)] = true; fuzzedAddressMapping[address(beacon)] = true; fuzzedAddressMapping[address(pufferProtocol)] = true; - fuzzedAddressMapping[address(pool)] = true; + fuzzedAddressMapping[address(validatorTicket)] = true; } function _deployContractAndSetupGuardians() public { @@ -133,18 +150,27 @@ contract TestHelper is Test, BaseScript { guardians[2] = guardian3; // Deploy everything with one script - PufferDeployment memory pufferDeployment = new DeployEverything().run(guardians, 1); + PufferProtocolDeployment memory pufferDeployment = new DeployEverything().run(guardians, 1); pufferProtocol = PufferProtocol(payable(pufferDeployment.pufferProtocol)); accessManager = AccessManager(pufferDeployment.accessManager); - pool = PufferPool(payable(pufferDeployment.pufferPool)); - withdrawalPool = IWithdrawalPool(pufferDeployment.withdrawalPool); verifier = IEnclaveVerifier(pufferDeployment.enclaveVerifier); guardianModule = GuardianModule(payable(pufferDeployment.guardianModule)); beacon = UpgradeableBeacon(pufferDeployment.beacon); moduleFactory = PufferModuleFactory(pufferDeployment.moduleFactory); + validatorTicket = ValidatorTicket(pufferDeployment.validatorTicket); + pufferOracle = PufferOracle(pufferDeployment.pufferOracle); + + // pufETH dependencies + pufferVault = PufferVaultMainnet(payable(pufferDeployment.pufferVault)); + pufferDepositor = PufferDepositor(payable(pufferDeployment.pufferDepositor)); + stETH = stETHMock(payable(pufferDeployment.stETH)); + weth = IWETH(payable(pufferDeployment.weth)); - vm.label(address(pool), "PufferPool"); + _upgradePufferVaultToMainnet(); + + vm.label(address(pufferVault), "PufferVault"); + vm.label(address(pufferDepositor), "PufferDepositor"); vm.label(address(pufferProtocol), "PufferProtocol"); Guardian1RaveEvidence guardian1Rave = new Guardian1RaveEvidence(); @@ -166,7 +192,7 @@ contract TestHelper is Test, BaseScript { verifier = guardianModule.ENCLAVE_VERIFIER(); verifier.addLeafX509(guardian1Rave.signingCert()); - require(keccak256(guardian1EnclavePubKey) == keccak256(guardian1Rave.payload()), "pubkeys dont match"); + require(keccak256(guardian1EnclavePubKey) == keccak256(guardian1Rave.payload()), "pubkeys don't match"); assertEq( blockhash(block.number), @@ -227,6 +253,33 @@ contract TestHelper is Test, BaseScript { assertEq(pubKeys[2], guardian3EnclavePubKey, "guardian3 pub key"); } + function _upgradePufferVaultToMainnet() internal { + // When we run any script in the test environment `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` is the msg.sender + // That means that the _deployer in scripts is that address + // Because of that, we grant it `upgrader`, so that it can run the upgrade script successfully + vm.startPrank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + accessManager.grantRole(1, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0); + + uint64 protocolRoleId = 12345; + accessManager.grantRole(protocolRoleId, address(pufferProtocol), 0); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = PufferVaultMainnet.transferETH.selector; + accessManager.setTargetFunctionRole(address(pufferVault), selectors, protocolRoleId); + vm.stopPrank(); + + _depositLiquidityToPufferVault(); + } + + function _depositLiquidityToPufferVault() internal { + // DEPOSIT 1k ETH to the pool so that we have enough liquidity for provisioning + vm.deal(LIQUIDITY_PROVIDER, 1000 ether); + + vm.startPrank(LIQUIDITY_PROVIDER); + pufferVault.depositETH{ value: 1000 ether }(LIQUIDITY_PROVIDER); + vm.stopPrank(); + } + function _getGuardianEOASignatures(bytes32 digest) internal view returns (bytes[] memory) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardian1SK, digest); bytes memory signature1 = abi.encodePacked(r, s, v); // note the order here is different from line above. @@ -264,13 +317,12 @@ contract TestHelper is Test, BaseScript { } // Modified from https://github.com/Vectorized/solady/blob/2ced0d8382fd0289932010517d66efb28b07c3ce/test/ERC20.t.sol - function _signPermit(_TestTemps memory t) internal view returns (Permit memory p) { + function _signPermit(_TestTemps memory t, bytes32 domainSeparator) internal view returns (Permit memory p) { bytes32 innerHash = keccak256(abi.encode(_PERMIT_TYPEHASH, t.owner, t.to, t.amount, t.nonce, t.deadline)); - bytes32 domainSeparator = pool.DOMAIN_SEPARATOR(); bytes32 outerHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, innerHash)); (t.v, t.r, t.s) = vm.sign(t.privateKey, outerHash); - return Permit({ owner: t.owner, deadline: t.deadline, amount: t.amount, v: t.v, r: t.r, s: t.s }); + return Permit({ deadline: t.deadline, amount: t.amount, v: t.v, r: t.r, s: t.s }); } function _testTemps(string memory seed, address to, uint256 amount, uint256 deadline) diff --git a/test/invariant/PufferProtocolInvariants.sol b/test/invariant/PufferProtocolInvariants.sol index f5b27a8f..bf15c3ba 100644 --- a/test/invariant/PufferProtocolInvariants.sol +++ b/test/invariant/PufferProtocolInvariants.sol @@ -1,69 +1,68 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { PufferPool } from "puffer/PufferPool.sol"; -import { PufferProtocolHandler } from "../handlers/PufferProtocolHandler.sol"; -import { TestHelper } from "../helpers/TestHelper.sol"; +// import { PufferProtocolHandler } from "../handlers/PufferProtocolHandler.sol"; +// import { TestHelper } from "../helpers/TestHelper.sol"; -contract PufferProtocolInvariants is TestHelper { - PufferProtocolHandler handler; +// contract PufferProtocolInvariants is TestHelper { +// PufferProtocolHandler handler; - function setUp() public override { - super.setUp(); +// function setUp() public override { +// super.setUp(); - vm.startPrank(DAO); - pufferProtocol.setValidatorLimitPerInterval(200); - vm.stopPrank(); - handler = - new PufferProtocolHandler(this, pool, withdrawalPool, pufferProtocol, guardiansEnclavePks, _broadcaster); +// vm.startPrank(DAO); +// pufferProtocol.setValidatorLimitPerInterval(200); +// vm.stopPrank(); +// handler = +// new PufferProtocolHandler(this, pool, withdrawalPool, pufferProtocol, guardiansEnclavePks, _broadcaster); - // Set handler as a target contract for invariant test - targetContract(address(handler)); - } +// // Set handler as a target contract for invariant test +// targetContract(address(handler)); +// } - function invariant_pufferPoolETHCanOnlyGoUp() public { - // PufferPool's ETH balance can only grow, unless it is `provisionNode` - if (handler.ethLeavingThePool()) { - assertLe(address(pool).balance, handler.previousBalance(), "balance should be smaller"); - } else { - assertGe(address(pool).balance, handler.previousBalance(), "balance should go up"); - } - } +// function invariant_pufferPoolETHCanOnlyGoUp() public { +// // PufferPool's ETH balance can only grow, unless it is `provisionNode` +// if (handler.ethLeavingThePool()) { +// assertLe(address(pool).balance, handler.previousBalance(), "balance should be smaller"); +// } else { +// assertGe(address(pool).balance, handler.previousBalance(), "balance should go up"); +// } +// } - // Make sure that the pufETH doesn't disappear - function invariant_pufferProtocolBond() public { - // Validate against ghost variable - uint256 pufETHinProtocol = pool.balanceOf(address(pufferProtocol)); - assertEq(handler.ghost_pufETH_bond_amount(), pufETHinProtocol, "missing bond from the protocol"); +// // Make sure that the pufETH doesn't disappear +// function invariant_pufferProtocolBond() public { +// // Validate against ghost variable +// uint256 pufETHinProtocol = pool.balanceOf(address(pufferProtocol)); +// assertEq(handler.ghost_pufETH_bond_amount(), pufETHinProtocol, "missing bond from the protocol"); - // Validate by calculating eth - uint256 ethAmount = pool.calculatePufETHtoETHAmount(pufETHinProtocol); - uint256 originalETHAmountDeposited = (handler.ghost_validators() * 1 ether); +// // Validate by calculating eth +// uint256 ethAmount = pool.calculatePufETHtoETHAmount(pufETHinProtocol); +// uint256 originalETHAmountDeposited = (handler.ghost_validators() * 1 ether); - // If the eth amount is lower than the original eth deposited, it is because of the rounding down (calculation for when we pay out users) - if (ethAmount < originalETHAmountDeposited) { - assertApproxEqRel( - ethAmount, - originalETHAmountDeposited, - 0.01e18, - "bond should be worth more than the number of validators depositing" - ); - } else { - assertGe( - ethAmount, - originalETHAmountDeposited, - "bond should be worth more than the number of validators depositing" - ); - } - } +// // If the eth amount is lower than the original eth deposited, it is because of the rounding down (calculation for when we pay out users) +// if (ethAmount < originalETHAmountDeposited) { +// assertApproxEqRel( +// ethAmount, +// originalETHAmountDeposited, +// 0.01e18, +// "bond should be worth more than the number of validators depositing" +// ); +// } else { +// assertGe( +// ethAmount, +// originalETHAmountDeposited, +// "bond should be worth more than the number of validators depositing" +// ); +// } +// } - // pufETH should always be worth more than ETH - function invariant_pufEthToETHRate() public { - // Exchange rate should always be bigger than 1:1, we are not supposed to be losing anything with this setup ATM - assertTrue(pool.getPufETHtoETHExchangeRate() >= 1 ether); - } +// // pufETH should always be worth more than ETH +// function invariant_pufEthToETHRate() public { +// // Exchange rate should always be bigger than 1:1, we are not supposed to be losing anything with this setup ATM +// assertTrue(pool.getPufETHtoETHExchangeRate() >= 1 ether); +// } - function invariant_callSummary() public view { - handler.callSummary(); - } -} +// function invariant_callSummary() public view { +// handler.callSummary(); +// } +// } diff --git a/test/mocks/EigenPodManagerMock.sol b/test/mocks/EigenPodManagerMock.sol index 8ec911b9..8676289d 100644 --- a/test/mocks/EigenPodManagerMock.sol +++ b/test/mocks/EigenPodManagerMock.sol @@ -6,7 +6,7 @@ import "eigenlayer/interfaces/IEigenPodManager.sol"; import { IBeacon } from "openzeppelin/proxy/beacon/IBeacon.sol"; contract EigenPodManagerMock is IEigenPodManager, Test { - function slasher() external view returns (ISlasher) { } + function slasher() external pure returns (ISlasher) { } function createPod() external pure returns (address) { return (address(123123123)); @@ -16,29 +16,29 @@ contract EigenPodManagerMock is IEigenPodManager, Test { return 55; } - function beaconChainETHStrategy() external view returns (IStrategy) { } + function beaconChainETHStrategy() external pure returns (IStrategy) { } - function eigenPodBeacon() external view returns (IBeacon) { + function eigenPodBeacon() external pure returns (IBeacon) { return IBeacon(address(99)); } - function ethPOS() external view returns (IETHPOSDeposit) { + function ethPOS() external pure returns (IETHPOSDeposit) { return IETHPOSDeposit(address(99)); } - function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32) { + function getBlockRootAtTimestamp(uint64 timestamp) external pure returns (bytes32) { return bytes32("asdf"); } - function maxPods() external view returns (uint256) { + function maxPods() external pure returns (uint256) { return 100; } - function numPods() external view returns (uint256) { + function numPods() external pure returns (uint256) { return 10; } - function podOwnerShares(address podOwner) external view returns (int256) { + function podOwnerShares(address podOwner) external pure returns (int256) { return 5; } diff --git a/test/mocks/PufferProtocolMockUpgrade.sol b/test/mocks/PufferProtocolMockUpgrade.sol index f4209b78..6bda9608 100644 --- a/test/mocks/PufferProtocolMockUpgrade.sol +++ b/test/mocks/PufferProtocolMockUpgrade.sol @@ -2,9 +2,10 @@ pragma solidity >=0.8.0 <0.9.0; import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { IPufferPool } from "puffer/interface/IPufferPool.sol"; -import { IWithdrawalPool } from "puffer/interface/IWithdrawalPool.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; +import { PufferVaultMainnet } from "pufETH/PufferVaultMainnet.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; contract PufferProtocolMockUpgrade is PufferProtocol { function returnSomething() external pure returns (uint256) { @@ -13,11 +14,11 @@ contract PufferProtocolMockUpgrade is PufferProtocol { constructor(address beacon) PufferProtocol( - IWithdrawalPool(address(0)), - IPufferPool(address(0)), + PufferVaultMainnet(payable(address(0))), GuardianModule(payable(address(0))), - payable(address(0)), - address(0) + address(0), + ValidatorTicket(address(0)), + IPufferOracle(address(0)) ) { } } diff --git a/test/mocks/SlasherMock.sol b/test/mocks/SlasherMock.sol index abf60d6a..45bc2610 100644 --- a/test/mocks/SlasherMock.sol +++ b/test/mocks/SlasherMock.sol @@ -509,7 +509,6 @@ contract SlasherMock is Initializable, OwnableUpgradeable, ISlasher, PausableMoc * be flipped to 'true', and we will use `getCorrectValueForInsertAfter` to find the correct input. This routine helps solve * a race condition where the proper value of `insertAfter` changes while a transaction is pending. */ - bool runFallbackRoutine = false; // If this condition is met, then the `updateBlock` input should be after `insertAfter`'s latest updateBlock if (insertAfter != HEAD) { diff --git a/test/unit/NoRestakingModule.t.sol b/test/unit/NoRestakingModule.t.sol index 10bab7c6..0deead3e 100644 --- a/test/unit/NoRestakingModule.t.sol +++ b/test/unit/NoRestakingModule.t.sol @@ -70,8 +70,8 @@ contract NoRestakingModuleTest is TestHelper { bytes32[][] memory merkleProofs = new bytes32[][](1); bytes32[] memory proof = new bytes32[](2); - proof[0] = hex"298198477089f9ce85de12fe6747b5d26250dd855e1e7eb15f067ae57ad400b5"; - proof[1] = hex"32c9503e9b3e27152cbd70e660517e961b8b3b7fe580fa76fa2f883025e1a3e2"; + proof[0] = hex"3c96586c7b865e20062ef47a0faca2d5358ecf9b5ebbef06016a674253b614c7"; + proof[1] = hex"c6f0836b2023b5fd91c6df8de68d3511c0e4e0984cd09df23d26227717a8ccb2"; merkleProofs[0] = proof; assertEq(alice.balance, 0, "alice should start with zero balance"); @@ -79,21 +79,15 @@ contract NoRestakingModuleTest is TestHelper { vm.startPrank(alice); _noRestakingModule.collectRewards({ node: alice, - pubKeyHash: keccak256(bytes.concat(bytes32("alice"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs }); // Double claim in different transactions should revert - vm.expectRevert( - abi.encodeWithSelector( - NoRestakingModule.AlreadyClaimed.selector, blockNumbers[0], keccak256(bytes.concat(bytes32("alice"))) - ) - ); + vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.AlreadyClaimed.selector, blockNumbers[0], alice)); _noRestakingModule.collectRewards({ node: alice, - pubKeyHash: keccak256(bytes.concat(bytes32("alice"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs @@ -102,26 +96,18 @@ contract NoRestakingModuleTest is TestHelper { // Bob claiming with Alice's proof vm.startPrank(bob); - vm.expectRevert( - abi.encodeWithSelector( - NoRestakingModule.AlreadyClaimed.selector, blockNumbers[0], keccak256(bytes.concat(bytes32("alice"))) - ) - ); + vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.NothingToClaim.selector, bob)); _noRestakingModule.collectRewards({ node: bob, - pubKeyHash: keccak256(bytes.concat(bytes32("alice"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs }); // Bob claiming with a valid proof that is not his - vm.expectRevert( - abi.encodeWithSelector(NoRestakingModule.NothingToClaim.selector, keccak256(bytes.concat(bytes32("bob")))) - ); + vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.NothingToClaim.selector, bob)); _noRestakingModule.collectRewards({ node: bob, - pubKeyHash: keccak256(bytes.concat(bytes32("bob"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs @@ -142,24 +128,19 @@ contract NoRestakingModuleTest is TestHelper { bytes32[][] memory merkleProofs = new bytes32[][](2); bytes32[] memory proof = new bytes32[](2); bytes32[] memory proof2 = new bytes32[](2); - proof[0] = hex"298198477089f9ce85de12fe6747b5d26250dd855e1e7eb15f067ae57ad400b5"; - proof[1] = hex"32c9503e9b3e27152cbd70e660517e961b8b3b7fe580fa76fa2f883025e1a3e2"; - proof2[0] = hex"298198477089f9ce85de12fe6747b5d26250dd855e1e7eb15f067ae57ad400b5"; - proof2[1] = hex"32c9503e9b3e27152cbd70e660517e961b8b3b7fe580fa76fa2f883025e1a3e2"; + proof[0] = hex"3c96586c7b865e20062ef47a0faca2d5358ecf9b5ebbef06016a674253b614c7"; + proof[1] = hex"c6f0836b2023b5fd91c6df8de68d3511c0e4e0984cd09df23d26227717a8ccb2"; + proof2[0] = hex"3c96586c7b865e20062ef47a0faca2d5358ecf9b5ebbef06016a674253b614c7"; + proof2[1] = hex"c6f0836b2023b5fd91c6df8de68d3511c0e4e0984cd09df23d26227717a8ccb2"; merkleProofs[0] = proof; merkleProofs[1] = proof2; assertEq(alice.balance, 0, "alice should start with zero balance"); vm.startPrank(alice); - vm.expectRevert( - abi.encodeWithSelector( - NoRestakingModule.AlreadyClaimed.selector, blockNumbers[0], keccak256(bytes.concat(bytes32("alice"))) - ) - ); + vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.AlreadyClaimed.selector, blockNumbers[0], alice)); _noRestakingModule.collectRewards({ node: alice, - pubKeyHash: keccak256(bytes.concat(bytes32("alice"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs @@ -168,10 +149,9 @@ contract NoRestakingModuleTest is TestHelper { // Zero withdrawal reverts function testCollectRewardsRevertsForZeroValues() public { - vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.NothingToClaim.selector, bytes32(0))); + vm.expectRevert(abi.encodeWithSelector(NoRestakingModule.NothingToClaim.selector, address(0))); _noRestakingModule.collectRewards({ node: address(0), - pubKeyHash: bytes32(0), blockNumbers: new uint256[](1), amounts: new uint256[](1), merkleProofs: new bytes32[][](1) @@ -190,7 +170,7 @@ contract NoRestakingModuleTest is TestHelper { bytes32[][] memory merkleProofs = new bytes32[][](1); bytes32[] memory proof = new bytes32[](1); - proof[0] = hex"23c812ec1c3edb02b46b62af473d75c341e2ceb95c39e712f5e24e97d4bcde4f"; + proof[0] = hex"1f10980ebf8a2fa0f1888e174f5487867589d59b15b3845792f5424c7a22e0f0"; merkleProofs[0] = proof; assertEq(charlie.balance, 0, "charlie should start with zero balance"); @@ -199,7 +179,6 @@ contract NoRestakingModuleTest is TestHelper { vm.startPrank(msgSender); _noRestakingModule.collectRewards({ node: charlie, - pubKeyHash: keccak256(bytes.concat(bytes32("charlie"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs @@ -222,11 +201,11 @@ contract NoRestakingModuleTest is TestHelper { bytes32[][] memory merkleProofs = new bytes32[][](2); bytes32[] memory proof1 = new bytes32[](2); - proof1[0] = hex"298198477089f9ce85de12fe6747b5d26250dd855e1e7eb15f067ae57ad400b5"; - proof1[1] = hex"32c9503e9b3e27152cbd70e660517e961b8b3b7fe580fa76fa2f883025e1a3e2"; + proof1[0] = hex"3c96586c7b865e20062ef47a0faca2d5358ecf9b5ebbef06016a674253b614c7"; + proof1[1] = hex"c6f0836b2023b5fd91c6df8de68d3511c0e4e0984cd09df23d26227717a8ccb2"; bytes32[] memory proof2 = new bytes32[](2); - proof2[0] = hex"6d4b23e4f81df0bb176d65cd2456a1d19f123228558bfaf7767d66690d600923"; - proof2[1] = hex"e6914098f54129e35649cf5d7c62ab7afaa3a2c709e226f55d716e7a75c64ad2"; + proof2[0] = hex"7cf8ac19900bd2891ad7ad3bbdef859e7b335aa1fe95774e3ec27eeda71831c8"; + proof2[1] = hex"80d19ea204fac2c5559ba004190fb74186d17b1eb00ddadb5d0c4935e8661a47"; merkleProofs[0] = proof1; merkleProofs[1] = proof2; @@ -234,7 +213,6 @@ contract NoRestakingModuleTest is TestHelper { _noRestakingModule.collectRewards({ node: alice, - pubKeyHash: keccak256(bytes.concat(bytes32("alice"))), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: merkleProofs @@ -244,7 +222,7 @@ contract NoRestakingModuleTest is TestHelper { } function testPostingRewardsForSameBlockReverts() public { - bytes32 merkleRoot1 = hex"4059b3b5d8c24bf58c7fab0ea81c2cd8409d7a26d9dc2c75f464945681d81371"; + bytes32 merkleRoot1 = hex"415eab63c87f7cb27d1ae7c58d634c68901523ff3773671cbdc09d2b002a80e1"; bytes32 signedMessageHash = LibGuardianMessages._getModuleRewardsRootMessage(bytes32("NO_RESTAKING"), merkleRoot1, 1); @@ -262,8 +240,8 @@ contract NoRestakingModuleTest is TestHelper { // Merkle roots are hardcoded, we have two of them vm.deal(address(_noRestakingModule), 1000 ether); - bytes32 merkleRoot1 = hex"4059b3b5d8c24bf58c7fab0ea81c2cd8409d7a26d9dc2c75f464945681d81371"; - bytes32 merkleRoot2 = hex"361520123168ffc3c2d93e1eaaaa5188616fef4a47f68e868a7414f2c2350313"; + bytes32 merkleRoot1 = hex"415eab63c87f7cb27d1ae7c58d634c68901523ff3773671cbdc09d2b002a80e1"; + bytes32 merkleRoot2 = hex"657fabde691fbafeb450b72bf921e575f1f822c2283513b7c67da69e9dac3429"; bytes32 signedMessageHash1 = LibGuardianMessages._getModuleRewardsRootMessage(bytes32("NO_RESTAKING"), merkleRoot1, 1); diff --git a/test/unit/NoRestakingStartegyProofs.js b/test/unit/NoRestakingStartegyProofs.js deleted file mode 100644 index eed6b13e..00000000 --- a/test/unit/NoRestakingStartegyProofs.js +++ /dev/null @@ -1,33 +0,0 @@ -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; -import fs from "fs"; - -// Values for proof #1 for NoRestakingStrategy.t.sol -const valuesProf1 = [ - ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "0xe1ab8030737e227464912c87005a255ebf7472264e511b5bc6925d5af301e5c2", "16080000000000000"], - ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "0x7b08d1ea016d45d5b69194738de2b0b7d134221e5e062e5f61566cf6440059d9", "16120000000000000"], - ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "0xb6778516d44d232c5fe78bce5c80b81bdc8966730a6b285d560f0ee5ab4ed209", "16070000000000000"], -]; - -// Values for proof #2 for NoRestakingStrategy.t.sol -const valuesProof2 = [ - ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "0xe1ab8030737e227464912c87005a255ebf7472264e511b5bc6925d5af301e5c2", "26080000000000000"], - ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "0x7b08d1ea016d45d5b69194738de2b0b7d134221e5e062e5f61566cf6440059d9", "36120000000000000"], - ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "0xb6778516d44d232c5fe78bce5c80b81bdc8966730a6b285d560f0ee5ab4ed209", "46070000000000000"], -]; - -// (2) -const tree = StandardMerkleTree.of(values, ["address", "bytes32", "uint256"]); - -// (3) -console.log('Merkle Root:', tree.root); - -// (4) -fs.writeFileSync("tree.json", JSON.stringify(tree.dump())); - -console.log(tree.render()); - -// Get proofs -for (const [i, v] of tree.entries()) { - const proof = tree.getProof(i); - console.log('Proof:', proof); -} \ No newline at end of file diff --git a/test/unit/NoRestakingStrategyProofs.js b/test/unit/NoRestakingStrategyProofs.js new file mode 100644 index 00000000..2a6966ac --- /dev/null +++ b/test/unit/NoRestakingStrategyProofs.js @@ -0,0 +1,33 @@ +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +import fs from "fs"; + +// Values for proof #1 for NoRestakingStrategy.t.sol +const valuesProf1 = [ + ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "16080000000000000"], + ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "16120000000000000"], + ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "16070000000000000"], +]; + +// Values for proof #2 for NoRestakingStrategy.t.sol +const valuesProof2 = [ + ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "26080000000000000"], + ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "36120000000000000"], + ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "46070000000000000"], +]; + +// (2) +const tree = StandardMerkleTree.of(values, ["address", "uint256"]); + +// (3) +console.log('Merkle Root:', tree.root); + +// (4) +fs.writeFileSync("tree.json", JSON.stringify(tree.dump())); + +console.log(tree.render()); + +// Get proofs +for (const [i, v] of tree.entries()) { + const proof = tree.getProof(i); + console.log('Proof:', proof); +} \ No newline at end of file diff --git a/test/unit/NoRestakingStrategyProofs.mjs b/test/unit/NoRestakingStrategyProofs.mjs new file mode 100644 index 00000000..2a6966ac --- /dev/null +++ b/test/unit/NoRestakingStrategyProofs.mjs @@ -0,0 +1,33 @@ +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +import fs from "fs"; + +// Values for proof #1 for NoRestakingStrategy.t.sol +const valuesProf1 = [ + ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "16080000000000000"], + ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "16120000000000000"], + ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "16070000000000000"], +]; + +// Values for proof #2 for NoRestakingStrategy.t.sol +const valuesProof2 = [ + ["0x328809Bc894f92807417D2dAD6b7C998c1aFdac6", "26080000000000000"], + ["0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e", "36120000000000000"], + ["0xea475d60c118d7058beF4bDd9c32bA51139a74e0", "46070000000000000"], +]; + +// (2) +const tree = StandardMerkleTree.of(values, ["address", "uint256"]); + +// (3) +console.log('Merkle Root:', tree.root); + +// (4) +fs.writeFileSync("tree.json", JSON.stringify(tree.dump())); + +console.log(tree.render()); + +// Get proofs +for (const [i, v] of tree.entries()) { + const proof = tree.getProof(i); + console.log('Proof:', proof); +} \ No newline at end of file diff --git a/test/unit/PufferModule.t.sol b/test/unit/PufferModule.t.sol index f6e7d78c..229bcb9a 100644 --- a/test/unit/PufferModule.t.sol +++ b/test/unit/PufferModule.t.sol @@ -107,12 +107,9 @@ contract PufferModuleTest is TestHelper { // Build a merkle proof for that MerkleProofData[] memory validatorRewards = new MerkleProofData[](3); - validatorRewards[0] = - MerkleProofData({ node: alice, pubKeyHash: keccak256(abi.encodePacked(alice)), amount: 0.01308 ether }); - validatorRewards[1] = - MerkleProofData({ node: bob, pubKeyHash: keccak256(abi.encodePacked(bob)), amount: 0.013 ether }); - validatorRewards[2] = - MerkleProofData({ node: charlie, pubKeyHash: keccak256(abi.encodePacked(charlie)), amount: 1 }); + validatorRewards[0] = MerkleProofData({ node: alice, amount: 0.01308 ether }); + validatorRewards[1] = MerkleProofData({ node: bob, amount: 0.013 ether }); + validatorRewards[2] = MerkleProofData({ node: charlie, amount: 1 }); vm.deal(module, 0.01308 ether + 0.013 ether + 1); bytes32 merkleRoot = _buildMerkleProof(validatorRewards); @@ -140,7 +137,6 @@ contract PufferModuleTest is TestHelper { vm.startPrank(alice); PufferModule(payable(module)).collectRewards({ node: alice, - pubKeyHash: keccak256(abi.encodePacked(alice)), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: aliceProofs @@ -148,14 +144,9 @@ contract PufferModuleTest is TestHelper { assertEq(alice.balance, 0.01308 ether, "alice should end with 0.01308 ether"); // Double claim in different transactions should revert - vm.expectRevert( - abi.encodeWithSelector( - PufferModule.AlreadyClaimed.selector, blockNumbers[0], keccak256(abi.encodePacked((alice))) - ) - ); + vm.expectRevert(abi.encodeWithSelector(PufferModule.AlreadyClaimed.selector, blockNumbers[0], alice)); PufferModule(payable(module)).collectRewards({ node: alice, - pubKeyHash: keccak256(abi.encodePacked(alice)), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: aliceProofs @@ -163,14 +154,9 @@ contract PufferModuleTest is TestHelper { // Bob claiming with Alice's proof (alice already claimed) vm.startPrank(bob); - vm.expectRevert( - abi.encodeWithSelector( - PufferModule.AlreadyClaimed.selector, blockNumbers[0], keccak256(abi.encodePacked(alice)) - ) - ); + vm.expectRevert(abi.encodeWithSelector(PufferModule.AlreadyClaimed.selector, blockNumbers[0], alice)); PufferModule(payable(module)).collectRewards({ - node: bob, - pubKeyHash: keccak256(abi.encodePacked(alice)), + node: alice, blockNumbers: blockNumbers, amounts: amounts, merkleProofs: aliceProofs @@ -186,12 +172,9 @@ contract PufferModuleTest is TestHelper { // Bob claiming with Charlie's prof (charlie did not claim yet) // It will revert with nothing to claim because the proof is not valid for bob - vm.expectRevert( - abi.encodeWithSelector(PufferModule.NothingToClaim.selector, keccak256(abi.encodePacked(charlie))) - ); + vm.expectRevert(abi.encodeWithSelector(PufferModule.NothingToClaim.selector, bob)); PufferModule(payable(module)).collectRewards({ node: bob, - pubKeyHash: keccak256(abi.encodePacked(charlie)), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: charlieProofs @@ -200,7 +183,6 @@ contract PufferModuleTest is TestHelper { // Bob claiming for charlie (bob is msg.sender) PufferModule(payable(module)).collectRewards({ node: charlie, - pubKeyHash: keccak256(abi.encodePacked(charlie)), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: charlieProofs @@ -213,7 +195,6 @@ contract PufferModuleTest is TestHelper { PufferModule(payable(module)).collectRewards({ node: bob, - pubKeyHash: keccak256(abi.encodePacked(bob)), blockNumbers: blockNumbers, amounts: amounts, merkleProofs: bobProofs @@ -237,9 +218,8 @@ contract PufferModuleTest is TestHelper { for (uint256 i = 0; i < validatorRewards.length; ++i) { MerkleProofData memory validatorData = validatorRewards[i]; - rewardsMerkleProofData[i] = keccak256( - bytes.concat(keccak256(abi.encode(validatorData.node, validatorData.pubKeyHash, validatorData.amount))) - ); + rewardsMerkleProofData[i] = + keccak256(bytes.concat(keccak256(abi.encode(validatorData.node, validatorData.amount)))); } root = rewardsMerkleProof.getRoot(rewardsMerkleProofData); @@ -248,6 +228,5 @@ contract PufferModuleTest is TestHelper { struct MerkleProofData { address node; - bytes32 pubKeyHash; uint256 amount; } diff --git a/test/unit/PufferPool.t.sol b/test/unit/PufferPool.t.sol deleted file mode 100644 index d63b58f1..00000000 --- a/test/unit/PufferPool.t.sol +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { Test } from "forge-std/Test.sol"; -import { PufferPool } from "puffer/PufferPool.sol"; -import { ECDSA } from "openzeppelin/utils/cryptography/ECDSA.sol"; -import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -import { TestHelper } from "../helpers/TestHelper.sol"; -import { PufferProtocol } from "puffer/PufferProtocol.sol"; -import { PufferPoolStorage } from "puffer/struct/PufferPoolStorage.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { IPufferPool } from "puffer/interface/IPufferPool.sol"; -import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; -import { LibGuardianMessages } from "puffer/LibGuardianMessages.sol"; - -contract Mock is ERC20 { - constructor() ERC20("mock", "mock") { - _mint(msg.sender, 1_000_000 ether); - } -} - -contract PufferPoolTest is TestHelper { - using ECDSA for bytes32; - using SafeTransferLib for address; - using SafeTransferLib for address payable; - - address rewardsRecipient = makeAddr("rewardsRecipient"); - - function setUp() public override { - // Just call the parent setUp() - super.setUp(); - _skipDefaultFuzzAddresses(); - } - - // Test setup - function testSetup() public { - assertEq(pool.name(), "Puffer ETH"); - assertEq(pool.symbol(), "pufETH"); - assertEq(pool.getPufETHtoETHExchangeRate(), FixedPointMathLib.WAD); - } - - // Fuzz test for depositing ETH to PufferPool - function testDeposit(address depositor, uint256 depositAmount) public assumeEOA(depositor) { - depositAmount = bound(depositAmount, 0.01 ether, 1_000_000 ether); - - vm.deal(depositor, depositAmount); - - uint256 expectedAmount = pool.calculateETHToPufETHAmount(depositAmount); - - vm.startPrank(depositor); - assertEq(pool.balanceOf(depositor), 0, "recipient pufETH amount before deposit"); - - uint256 minted = pool.depositETH{ value: depositAmount }(); - vm.stopPrank(); - - uint256 expectedETH = pool.calculatePufETHtoETHAmount(minted); - assertEq(expectedETH, minted, "amounts should match 1:1 ratio"); - - assertEq(pool.balanceOf(depositor), depositAmount, "recipient pufETH amount"); - assertEq(expectedAmount, depositAmount, "recipient pufETH calculated amount"); - } - - // Fuzz test to test rounding error for exchange rate 1:1 - function testDepositAndRedeemRoundingError(address depositor, uint256 depositAmount) public assumeEOA(depositor) { - depositAmount = bound(depositAmount, 0.01 ether, 1_000_000 ether); - - // Give out ETH - vm.deal(depositor, depositAmount); - vm.deal(address(withdrawalPool), 10_000_000 ether); - - vm.startPrank(depositor); - assertEq(pool.balanceOf(depositor), 0, "recipient pufETH amount before deposit"); - - uint256 minted = pool.depositETH{ value: depositAmount }(); - - pool.approve(address(withdrawalPool), type(uint256).max); - uint256 withdrawAmount = withdrawalPool.withdrawETH(depositor, minted); - - vm.stopPrank(); - - assertTrue(depositAmount >= withdrawAmount, "rounding error"); - } - - // Fuzz test to test rounding error - function testDepositAndRedeemRoundingErrorForDifferentExchangeRate(address depositor, uint256 depositAmount) - public - assumeEOA(depositor) - { - vm.roll(50401); - - depositAmount = bound(depositAmount, 0.01 ether, 1_000_000 ether); - - // Give out ETH - vm.deal(depositor, depositAmount); - vm.deal(address(withdrawalPool), 10_000_000 ether); - - assertEq(pool.getPufETHtoETHExchangeRate(), 1 ether, "exchange rate before"); - - pufferProtocol.proofOfReserve({ - ethAmount: 10_000 ether, - lockedETH: 320 ether, - pufETHTotalSupply: 10_000 ether, - blockNumber: 50350, - numberOfActiveValidators: 100, - guardianSignatures: _getGuardianEOASignatures( - LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: 10_000 ether, - lockedETH: 320 ether, - pufETHTotalSupply: 10_000 ether, - numberOfActiveValidators: 100, - blockNumber: 50350 - }) - ) - }); - vm.stopPrank(); - - assertEq(pool.getPufETHtoETHExchangeRate(), 1.032 ether, "exchange rate after"); - - vm.startPrank(depositor); - assertEq(pool.balanceOf(depositor), 0, "recipient pufETH amount before deposit"); - - uint256 minted = pool.depositETH{ value: depositAmount }(); - - pool.approve(address(withdrawalPool), type(uint256).max); - uint256 withdrawAmount = withdrawalPool.withdrawETH(depositor, minted); - - vm.stopPrank(); - - assertTrue(depositAmount >= withdrawAmount, "rounding error"); - } - - // Test Alice and Bob depositing - function testMultipleDeposits() public { - address bob = makeAddr("bob"); - address alice = makeAddr("alice"); - - vm.deal(bob, 100 ether); - vm.deal(alice, 100 ether); - - vm.startPrank(bob); - pool.depositETH{ value: 6 ether }(); - assertEq(pool.balanceOf(bob), 6 ether, "bob balance"); - - vm.startPrank(alice); - uint256 minted = pool.depositETH{ value: 10 ether }(); - - assertEq(minted, 10 ether, "amounts dont match"); - assertEq(pool.balanceOf(alice), 10 ether, "alice balance"); - } - - // Test burning of pufETH - function testBurn(address depositor) public assumeEOA(depositor) { - uint256 amount = 5 ether; - vm.deal(depositor, amount); - - vm.startPrank(depositor); - uint256 pufETHAmount = pool.depositETH{ value: amount }(); - assertTrue(pufETHAmount != 0, "invalid pufETH amount"); - - pool.burn(pufETHAmount); - - assertTrue(0 == pool.balanceOf(depositor)); - } - - function testDepositForOneWei() public { - uint256 minted = pool.depositETH{ value: 1 }(); - assertEq(minted, 1, "minted 1 wei"); - } - - function testRatioChange() public { - // total supply 0 means ratio is 1:1 - uint256 minted = pool.depositETH{ value: 1 ether }(); - assertEq(minted, 1 ether, "minted amount"); - - // Simulate rewards of 1 ETH - address(pool).safeTransferETH(1 ether); - - // Fast forward 50400 blocks ~ 7 days - vm.roll(50401); - - pufferProtocol.proofOfReserve({ - ethAmount: 2 ether, - lockedETH: 0, - pufETHTotalSupply: 1 ether, - blockNumber: 50401, - numberOfActiveValidators: 100, - guardianSignatures: _getGuardianEOASignatures( - LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: 2 ether, - lockedETH: 0 ether, - pufETHTotalSupply: 1 ether, - numberOfActiveValidators: 100, - blockNumber: 50401 - }) - ) - }); - vm.stopPrank(); - - // total supply is 1 - // total eth = 2 - // ratio is 1/2 = 0.5, mint 0.5 pufETH to caller - - minted = pool.depositETH{ value: 1 ether }(); - - assertEq(minted, 0.5 ether, "ratio didn't change"); - } - - function testRatioChangeSandwichAttack(uint256 numberOfValidators, uint256 attackerAmount) public { - numberOfValidators = bound(numberOfValidators, 10, 1000); - - attackerAmount = bound(attackerAmount, 1 ether, 100 ether); - address attacker = makeAddr("attacker"); - - vm.deal(attacker, attackerAmount); - - uint256 startAmountInTheSystem = numberOfValidators * 32 ether; - // Imagine that we have 50 validators = 1600 ETH - // Daily reward amount ~ 50 * 0.00237 ETH => 0.1185 ETH - vm.deal(address(withdrawalPool), startAmountInTheSystem); - - // total supply 0 means ratio is 1:1 - uint256 gasBefore = gasleft(); - uint256 minted = pool.depositETH{ value: 10 ether }(); - uint256 gasAfter = gasleft(); - assertEq(minted, 10 ether, "minted amount"); - - uint256 GWEI = 1000000000; - uint256 gasConsumedForDeposit = (gasBefore - gasAfter) * GWEI; // gas * gwei to get ETH amount; - - // Say that we got 10 ETH in rewards today - - vm.startPrank(attacker); - uint256 attackerMinted = pool.depositETH{ value: attackerAmount }(); - pool.approve(address(withdrawalPool), type(uint256).max); - vm.stopPrank(); - - uint256 averageDailyRewradAmount = 0.00237 ether; - - // Change withdrawal pool amount to start amount + daily rewards - vm.deal(address(withdrawalPool), startAmountInTheSystem + (numberOfValidators * averageDailyRewradAmount)); - - vm.startPrank(attacker); - gasBefore = gasleft(); - withdrawalPool.withdrawETH(attacker, attackerMinted); - gasAfter = gasleft(); - vm.stopPrank(); - - uint256 gasConsumedForWithdrawal = (gasBefore - gasAfter) * GWEI; // gas * gwei to get ETH amount; - - assertTrue( - attacker.balance < (attackerAmount - (gasConsumedForWithdrawal + gasConsumedForDeposit)), - "attacker is in profit" - ); - } - - function testMintZero() public { - vm.expectRevert(IPufferPool.InvalidETHAmount.selector); - uint256 minted = pool.depositETH{ value: 0 }(); - } - - function testStorageS() public { - PufferPoolStorage memory data = pufferProtocol.getPufferPoolStorage(); - assertEq(data.lastUpdate, 0, "last update"); - } - - function testRecoverERC20() public { - vm.expectRevert(abi.encodeWithSelector(IPufferPool.InvalidToken.selector, address(pool))); - pool.recoverERC20(address(pool)); - - ERC20 token = new Mock(); - token.transfer(address(pool), token.balanceOf(address(this))); - - assertEq(token.balanceOf(pufferProtocol.TREASURY()), 0, "token balance"); - - pool.recoverERC20(address(token)); - - assertEq(token.balanceOf(pufferProtocol.TREASURY()), 1_000_000 ether, "token balance after"); - } -} diff --git a/test/unit/PufferProtocol.t.sol b/test/unit/PufferProtocol.t.sol index e8339112..75e6fbc4 100644 --- a/test/unit/PufferProtocol.t.sol +++ b/test/unit/PufferProtocol.t.sol @@ -12,11 +12,14 @@ import { Validator } from "puffer/struct/Validator.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModule } from "puffer/PufferModule.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; -import { ROLE_ID_DAO, ROLE_ID_PUFFER_PROTOCOL } from "script/SetupAccess.s.sol"; +import { IPufferOracle } from "pufETH/interface/IPufferOracle.sol"; +import { ROLE_ID_PUFFER_PROTOCOL, ROLE_ID_DAO } from "pufETHScript/Roles.sol"; import { Unauthorized } from "puffer/Errors.sol"; import { LibGuardianMessages } from "puffer/LibGuardianMessages.sol"; import { Permit } from "puffer/struct/Permit.sol"; import { Merkle } from "murky/Merkle.sol"; +import { StoppedValidatorInfo } from "puffer/struct/StoppedValidatorInfo.sol"; +import { console } from "forge-std/console.sol"; contract PufferProtocolTest is TestHelper { using ECDSA for bytes32; @@ -35,19 +38,29 @@ contract PufferProtocolTest is TestHelper { bytes32 constant EIGEN_DA = bytes32("EIGEN_DA"); bytes32 constant CRAZY_GAINS = bytes32("CRAZY_GAINS"); + Permit emptyPermit; + + // 0.01 % + uint256 pointZeroZeroOne = 0.0001e18; + // 0.02 % + uint256 pointZeroZeroTwo = 0.0002e18; + // 0.05 % + uint256 pointZeroFive = 0.0005e18; + // 0.1% diff + uint256 pointZeroOne = 0.001e18; + + address alice = makeAddr("alice"); + function setUp() public override { super.setUp(); vm.deal(address(this), 1000 ether); // Setup roles - bytes4[] memory selectors = new bytes4[](6); - selectors[0] = PufferProtocol.setProtocolFeeRate.selector; - selectors[1] = PufferProtocol.setSmoothingCommitments.selector; - selectors[2] = PufferProtocol.createPufferModule.selector; - selectors[3] = PufferProtocol.setModuleWeights.selector; - selectors[4] = PufferProtocol.setValidatorLimitPerInterval.selector; - selectors[5] = bytes4(hex"4f1ef286"); // signature for UUPS.upgradeToAndCall(address newImplementation, bytes memory data) + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = PufferProtocol.createPufferModule.selector; + selectors[1] = PufferProtocol.setModuleWeights.selector; + selectors[2] = bytes4(hex"4f1ef286"); // signature for UUPS.upgradeToAndCall(address newImplementation, bytes memory data) // For simplicity grant DAO role to this contract vm.startPrank(_broadcaster); @@ -55,20 +68,26 @@ contract PufferProtocolTest is TestHelper { accessManager.grantRole(ROLE_ID_DAO, address(this), 0); vm.stopPrank(); + // Set daily withdrawals limit + pufferVault.setDailyWithdrawalLimit(1000 ether); + _skipDefaultFuzzAddresses(); fuzzedAddressMapping[address(pufferProtocol)] = true; } // Setup - function testSetup() public { - assertTrue(address(pufferProtocol.WITHDRAWAL_POOL()) != address(0), "non zero address"); - assertTrue(address(pufferProtocol.POOL()) != address(0), "puffer pool address"); + function test_setup() public { + assertTrue(address(pufferProtocol.PUFFER_VAULT()) != address(0), "puffer vault address"); address module = pufferProtocol.getModuleAddress(NO_RESTAKING); assertEq(PufferModule(payable(module)).NAME(), NO_RESTAKING, "bad name"); } - function testEmptyQueue() public { + function test_register_validator_key() public { + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + } + + function test_empty_queue() public { (bytes32 moduleName, uint256 idx) = pufferProtocol.getNextValidatorToProvision(); assertEq(moduleName, bytes32("NO_VALIDATORS"), "name"); @@ -76,9 +95,7 @@ contract PufferProtocolTest is TestHelper { } // Test Skipping the validator - function testSkipProvisioning() public { - vm.deal(address(pool), 1000 ether); - + function test_skip_provisioning() public { _registerValidatorKey(bytes32("alice"), NO_RESTAKING); _registerValidatorKey(bytes32("bob"), NO_RESTAKING); @@ -89,12 +106,12 @@ contract PufferProtocolTest is TestHelper { assertEq(idx, 0, "idx"); assertEq(moduleSelectionIndex, 0, "module selection idx"); - assertTrue(pool.balanceOf(address(this)) == 0, "zero pufETH"); + assertTrue(pufferVault.balanceOf(address(this)) == 0, "zero pufETH"); pufferProtocol.skipProvisioning(NO_RESTAKING, _getGuardianSignaturesForSkipping()); // This contract should receive pufETH because of the skipProvisioning - assertTrue(pool.balanceOf(address(this)) != 0, "non zero pufETH"); + assertTrue(pufferVault.balanceOf(address(this)) != 0, "non zero pufETH"); Validator memory aliceValidator = pufferProtocol.getValidatorInfo(NO_RESTAKING, 0); assertTrue(aliceValidator.status == Status.SKIPPED, "did not update status"); @@ -108,58 +125,60 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("bob")), 1, NO_RESTAKING); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); moduleSelectionIndex = pufferProtocol.getModuleSelectIndex(); assertEq(moduleSelectionIndex, 1, "module idx changed"); } // Create an existing module should revert - function testCreateExistingModuleShouldFail() public { + function test_create_existing_module_fails() public { vm.startPrank(DAO); vm.expectRevert(IPufferProtocol.ModuleAlreadyExists.selector); pufferProtocol.createPufferModule(NO_RESTAKING, "", address(0)); } // Invalid pub key shares length - function testRegisterInvalidPubKeyShares() public { + function test_register_invalid_pubkey_shares_length() public { ValidatorKeyData memory data = _getMockValidatorKeyData(new bytes(48), NO_RESTAKING); data.blsPubKeySet = new bytes(22); + Permit memory permit; + vm.expectRevert(IPufferProtocol.InvalidBLSPublicKeySet.selector); - pufferProtocol.registerValidatorKey{ value: 4 ether }(data, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: 4 ether }(data, NO_RESTAKING, 30, emptyPermit, emptyPermit); } // Invalid private key shares length - function testRegisterInvalidPrivKeyShares() public { + function test_register_invalid_privKey_shares() public { ValidatorKeyData memory data = _getMockValidatorKeyData(new bytes(48), NO_RESTAKING); data.blsEncryptedPrivKeyShares = new bytes[](2); vm.expectRevert(IPufferProtocol.InvalidBLSPrivateKeyShares.selector); - pufferProtocol.registerValidatorKey{ value: 4 ether }(data, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: 4 ether }(data, NO_RESTAKING, 30, emptyPermit, emptyPermit); } // Try registering with invalid module - function testRegisterToInvalidModule() public { - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); + function test_register_to_invalid_module() public { + uint256 smoothingCommitment = pufferOracle.getValidatorTicketPrice() * 30; bytes memory pubKey = _getPubKey(bytes32("charlie")); ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, NO_RESTAKING); vm.expectRevert(IPufferProtocol.ValidatorLimitForModuleReached.selector); pufferProtocol.registerValidatorKey{ value: smoothingCommitment }( - validatorKeyData, bytes32("imaginary module"), 1 + validatorKeyData, bytes32("imaginary module"), 30, emptyPermit, emptyPermit ); } // Try registering with invalid amount paid - function testRegisterWithInvalidAmountPaid() public { + function test_register_with_invalid_amount_paid() public { bytes memory pubKey = _getPubKey(bytes32("charlie")); ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, NO_RESTAKING); vm.expectRevert(IPufferProtocol.InvalidETHAmount.selector); - pufferProtocol.registerValidatorKey{ value: 5 ether }(validatorKeyData, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: 5 ether }( + validatorKeyData, NO_RESTAKING, 30, emptyPermit, emptyPermit + ); } - function testModuleDOS() external { - vm.deal(address(pool), 1000 ether); - + function test_module_DOS() external { bytes32[] memory weights = pufferProtocol.getModuleWeights(); assertEq(weights.length, 1, "only one module"); assertEq(weights[0], NO_RESTAKING, "no restaking"); @@ -178,20 +197,20 @@ contract PufferProtocolTest is TestHelper { // If we stop registration for 0, it will advance the counter // Simulate that somebody registered more validators - // pufferProtocol.stopRegistration(NO_RESTAKING, 0); - pufferProtocol.stopRegistration(NO_RESTAKING, 1); - pufferProtocol.stopRegistration(NO_RESTAKING, 2); - pufferProtocol.stopRegistration(NO_RESTAKING, 3); - pufferProtocol.stopRegistration(NO_RESTAKING, 4); + // pufferProtocol.cancelRegistration(NO_RESTAKING, 0); + pufferProtocol.cancelRegistration(NO_RESTAKING, 1); + pufferProtocol.cancelRegistration(NO_RESTAKING, 2); + pufferProtocol.cancelRegistration(NO_RESTAKING, 3); + pufferProtocol.cancelRegistration(NO_RESTAKING, 4); // Skip 5, we want to provision 5 - pufferProtocol.stopRegistration(NO_RESTAKING, 6); - pufferProtocol.stopRegistration(NO_RESTAKING, 7); + pufferProtocol.cancelRegistration(NO_RESTAKING, 6); + pufferProtocol.cancelRegistration(NO_RESTAKING, 7); (bytes32 module, uint256 idx) = pufferProtocol.getNextValidatorToProvision(); assertEq(module, NO_RESTAKING, "module"); assertEq(idx, 0, "idx"); - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice")))); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 0); uint256 next = pufferProtocol.getNextValidatorToBeProvisionedIndex(NO_RESTAKING); assertEq(next, 1, "next idx"); @@ -201,7 +220,7 @@ contract PufferProtocolTest is TestHelper { assertEq(idx, 5, "idx"); // Provision node updates the idx to current + 1 - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("ford")))); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("ford"))), 0); // That idx is 6 next = pufferProtocol.getNextValidatorToBeProvisionedIndex(NO_RESTAKING); @@ -213,136 +232,86 @@ contract PufferProtocolTest is TestHelper { assertEq(idx, 8, "idx"); } - // Test extending validator commitment - function testExtendCommitment() public { - _registerValidatorKey(bytes32("alice"), NO_RESTAKING); - - Validator memory validator = pufferProtocol.getValidatorInfo(NO_RESTAKING, 0); - assertTrue(validator.node == address(this), "node operator"); - - vm.warp(1000); - - // Amounts don't match - vm.expectRevert(IPufferProtocol.InvalidETHAmount.selector); - pufferProtocol.extendCommitment{ value: 5 ether }(NO_RESTAKING, 0, 5); - - // Should extend - pufferProtocol.extendCommitment{ value: pufferProtocol.getSmoothingCommitment(5) }(NO_RESTAKING, 0, 5); - - validator = pufferProtocol.getValidatorInfo(NO_RESTAKING, 0); - - assertTrue(validator.monthsCommitted == 5, "lastPayment"); - } - // Try updating for future block - function testProofOfReserve() external { + function test_proof_of_reserve() external { vm.roll(50401); - pufferProtocol.proofOfReserve({ - ethAmount: 2 ether, - lockedETH: 32 ether, - pufETHTotalSupply: 1 ether, + pufferOracle.proofOfReserve({ + newLockedETH: 32 ether, blockNumber: 50401, - numberOfActiveValidators: 100, + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 1000, guardianSignatures: _getGuardianEOASignatures( LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: 2 ether, lockedETH: 32 ether, - numberOfActiveValidators: 100, - pufETHTotalSupply: 1 ether, - blockNumber: 50401 + blockNumber: 50401, + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 1000 }) ) }); bytes[] memory signatures2 = _getGuardianEOASignatures( LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: 2 ether, - lockedETH: 0 ether, - numberOfActiveValidators: 100, - pufETHTotalSupply: 1 ether, - blockNumber: 50401 + lockedETH: 0, + blockNumber: 50401, + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 1000 }) ); // Second update should revert as it has not passed enough time between two updates - vm.expectRevert(IPufferProtocol.OutsideUpdateWindow.selector); - pufferProtocol.proofOfReserve({ - ethAmount: 2 ether, - lockedETH: 0, - pufETHTotalSupply: 1 ether, + vm.expectRevert(IPufferOracle.OutsideUpdateWindow.selector); + pufferOracle.proofOfReserve({ + newLockedETH: 0, blockNumber: 50401, - numberOfActiveValidators: 100, + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 1000, guardianSignatures: signatures2 }); } - function testBurstThreshold() external { + function test_burst_threshold() external { vm.roll(50401); // Update the reserves and make it so that the next validator is over threshold - pufferProtocol.proofOfReserve({ - ethAmount: 2 ether, - lockedETH: 32 ether, - pufETHTotalSupply: 1 ether, + pufferOracle.proofOfReserve({ + newLockedETH: 32 ether, blockNumber: 50401, - numberOfActiveValidators: 1, + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 10, guardianSignatures: _getGuardianEOASignatures( LibGuardianMessages._getProofOfReserveMessage({ - ethAmount: 2 ether, lockedETH: 32 ether, - pufETHTotalSupply: 1 ether, blockNumber: 50401, - numberOfActiveValidators: 1 + numberOfActivePufferValidators: 10, + totalNumberOfValidators: 10 }) ) }); - uint256 balanceBefore = pufferProtocol.TREASURY().balance; + uint256 sc = pufferOracle.getValidatorTicketPrice() * 30; - uint256 sc = pufferProtocol.getSmoothingCommitment(1); + uint256 balanceBefore = address(validatorTicket).balance; _registerValidatorKey(bytes32("alice"), NO_RESTAKING); - uint256 balanceAfter = pufferProtocol.TREASURY().balance; + uint256 balanceAfter = address(validatorTicket).balance; assertEq(balanceAfter, balanceBefore + sc, "treasury gets everything"); } // Set validator limit and try registering that many validators - function testFuzzRegisterManyValidators(uint8 numberOfValidatorsToProvision) external { - pufferProtocol.setValidatorLimitPerInterval(numberOfValidatorsToProvision); + function test_fuzz_register_many_validators(uint8 numberOfValidatorsToProvision) external { for (uint256 i = 0; i < uint256(numberOfValidatorsToProvision); ++i) { vm.deal(address(this), 2 ether); _registerValidatorKey(bytes32(i), NO_RESTAKING); } } - // Change smoothing commitment for default module - function testSetSmoothingCommitment() external { - uint256 commitmentBefore = pufferProtocol.getSmoothingCommitment(1); - uint256[] memory commitments = new uint256[](1); - commitments[0] = 20 ether; - pufferProtocol.setSmoothingCommitments(commitments); - uint256 commitmentAfter = pufferProtocol.getSmoothingCommitment(1); - assertEq(commitmentAfter, 20 ether, "after"); - assertTrue(commitmentBefore != commitmentAfter, "should change"); - } - - // Change smoothing non existent module - function testSetSmoothingCommitment(bytes32 module) external { - uint256 commitmentBefore = pufferProtocol.getSmoothingCommitment(1); - uint256[] memory commitments = new uint256[](1); - commitments[0] = 20 ether; - pufferProtocol.setSmoothingCommitments(commitments); - uint256 commitmentAfter = pufferProtocol.getSmoothingCommitment(1); - assertEq(commitmentAfter, 20 ether, "after"); - assertTrue(commitmentBefore != commitmentAfter, "should change"); - } - // Try registering without RAVE evidence - function testRegisterWithoutRAVE() public { - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); + function test_register_no_sgx() public { + uint256 vtPrice = pufferOracle.getValidatorTicketPrice() * 30; bytes memory pubKey = _getPubKey(bytes32("something")); @@ -362,12 +331,14 @@ contract PufferProtocolTest is TestHelper { raveEvidence: new bytes(0) // No rave }); - pufferProtocol.registerValidatorKey{ value: smoothingCommitment + 2 ether }(validatorData, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: vtPrice + 2 ether }( + validatorData, NO_RESTAKING, 30, emptyPermit, emptyPermit + ); } // Try registering with invalid BLS key length - function testRegisterWithInvalidBLSPubKey() public { - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); + function test_register_invalid_bls_key() public { + uint256 smoothingCommitment = pufferOracle.getValidatorTicketPrice(); bytes memory pubKey = hex"aeaa"; @@ -388,12 +359,14 @@ contract PufferProtocolTest is TestHelper { }); vm.expectRevert(IPufferProtocol.InvalidBLSPubKey.selector); - pufferProtocol.registerValidatorKey{ value: smoothingCommitment }(validatorData, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: smoothingCommitment }( + validatorData, NO_RESTAKING, 30, emptyPermit, emptyPermit + ); } - function testGetPayload() public { + function test_get_payload() public { (bytes[] memory guardianPubKeys, bytes memory withdrawalCredentials, uint256 threshold, uint256 ethAmount) = - pufferProtocol.getPayload(NO_RESTAKING, false, 1); + pufferProtocol.getPayload(NO_RESTAKING, false, 30); assertEq(guardianPubKeys[0], guardian1EnclavePubKey, "guardian1"); assertEq(guardianPubKeys[1], guardian2EnclavePubKey, "guardian2"); @@ -403,27 +376,8 @@ contract PufferProtocolTest is TestHelper { assertEq(threshold, 1, "threshold"); } - // Try registering more validators than the allowed number - function testRegisterMoreValidatorsThanTheLimit() public { - uint256 previousInterval = pufferProtocol.getValidatorLimitPerInterval(); - assertEq(previousInterval, 20, "previous limit"); - pufferProtocol.setValidatorLimitPerInterval(2); - uint256 newInterval = pufferProtocol.getValidatorLimitPerInterval(); - assertEq(newInterval, 2, "new limit"); - - _registerValidatorKey(bytes32("alice"), NO_RESTAKING); - _registerValidatorKey(bytes32("bob"), NO_RESTAKING); - - // Third one should revert - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); - bytes memory pubKey = _getPubKey(bytes32("charlie")); - ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, NO_RESTAKING); - vm.expectRevert(IPufferProtocol.ValidatorLimitPerIntervalReached.selector); - pufferProtocol.registerValidatorKey{ value: smoothingCommitment }(validatorKeyData, NO_RESTAKING, 1); - } - // Try to provision a validator when there is nothing to provision - function testProvisioning() public { + function test_provision_reverts() public { (bytes32 moduleName, uint256 idx) = pufferProtocol.getNextValidatorToProvision(); assertEq(type(uint256).max, idx, "module"); @@ -431,142 +385,122 @@ contract PufferProtocolTest is TestHelper { _getGuardianSignatures(hex"0000000000000000000000000000000000000000000000000000000000000000"); vm.expectRevert(); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); } - function testSetProtocolFeeRate() public { - uint256 rate = 10 * FixedPointMathLib.WAD; - pufferProtocol.setProtocolFeeRate(rate); // 10% - assertEq(pufferProtocol.getProtocolFeeRate(), rate, "new rate"); + // function testSetProtocolFeeRate() public { + // uint256 rate = 10 * FixedPointMathLib.WAD; + // pufferProtocol.setProtocolFeeRate(rate); // 10% + // assertEq(pufferProtocol.getProtocolFeeRate(), rate, "new rate"); + // } + + // function testSetGuardiansFeeRateOverTheLimit() public { + // uint256 rate = 30 * FixedPointMathLib.WAD; + // vm.expectRevert(IPufferProtocol.InvalidData.selector); + // pufferProtocol.setGuardiansFeeRate(rate); + // } + + // function testSetProtocolFeeRateOverTheLimit() public { + // uint256 rate = 30 * FixedPointMathLib.WAD; + // vm.expectRevert(IPufferProtocol.InvalidData.selector); + // pufferProtocol.setProtocolFeeRate(rate); + // } + + function test_fee_calculations() public { + // // Default values are + // // 2% guardians + // // 10% withdrawal fee pool + // // 0.5% guardians + // // rest to the PufferPool + // uint256 amount = pufferOracle.getValidatorTicketPrice(); + + // assertEq(0, pufferProtocol.TREASURY().balance, "zero treasury"); + // assertEq(0, address(pufferProtocol.GUARDIAN_MODULE()).balance, "zero guardians"); + // assertEq(1000 ether, address(pufferProtocol.PUFFER_VAULT()).balance, "starting vault balance"); + + // // We don't have additional validations on if the validator is active or not + // // pufferProtocol.extendCommitment{ value: amount }(NO_RESTAKING, 0, 12); + + // assertEq(2280296714778796, pufferProtocol.TREASURY().balance, "non zero treasury"); + // assertEq(570074178694699, address(pufferProtocol.GUARDIAN_MODULE()).balance, "non zero guardians"); + // assertEq(100048018360919684, address(pufferProtocol.PUFFER_VAULT()).balance, "non zero pool"); } - function testSetGuardiansFeeRateOverTheLimit() public { - uint256 rate = 30 * FixedPointMathLib.WAD; - vm.expectRevert(IPufferProtocol.InvalidData.selector); - pufferProtocol.setGuardiansFeeRate(rate); - } - - function testSetProtocolFeeRateOverTheLimit() public { - uint256 rate = 30 * FixedPointMathLib.WAD; - vm.expectRevert(IPufferProtocol.InvalidData.selector); - pufferProtocol.setProtocolFeeRate(rate); - } - - function testSetWithdrawalPoolRateMax() public { - uint256 rate = 100 * FixedPointMathLib.WAD; - vm.expectEmit(true, true, true, true); - emit IPufferProtocol.WithdrawalPoolRateChanged(10 * FixedPointMathLib.WAD, 100 * FixedPointMathLib.WAD); - pufferProtocol.setWithdrawalPoolRate(rate); - } - - function testFeeCalculations() public { - // Default values are - // 2% guardians - // 10% withdrawal fee pool - // 0.5% guardians - // rest to the PufferPool - uint256 amount = pufferProtocol.getSmoothingCommitment(12); - - assertEq(0, pufferProtocol.TREASURY().balance, "zero treasury"); - assertEq(0, address(pufferProtocol.GUARDIAN_MODULE()).balance, "zero guardians"); - assertEq(0, address(pufferProtocol.POOL()).balance, "zero pool"); - assertEq(0, address(pufferProtocol.WITHDRAWAL_POOL()).balance, "withdrawal pool balance"); - - // We don't have additional validations on if the validator is active or not - pufferProtocol.extendCommitment{ value: amount }(NO_RESTAKING, 0, 12); - - assertEq(2280296714778796, pufferProtocol.TREASURY().balance, "non zero treasury"); - assertEq(570074178694699, address(pufferProtocol.GUARDIAN_MODULE()).balance, "non zero guardians"); - assertEq(11116446484546631, address(pufferProtocol.WITHDRAWAL_POOL()).balance, "withdrawal pool balance"); - assertEq(100048018360919684, address(pufferProtocol.POOL()).balance, "non zero pool"); + function test_change_module() public { + vm.expectRevert(IPufferProtocol.InvalidPufferModule.selector); + pufferProtocol.changeModule(NO_RESTAKING, PufferModule(payable(address(5)))); } - function testFeeCalculationsEverythingToWithdrawalPool() public { - // Set withdrawal fee to 100% - pufferProtocol.setWithdrawalPoolRate(100 * FixedPointMathLib.WAD); - - // Default values are - // 2% guardians - // 0.5% guardians - // 100% to the withdrawal pool - // 0 to puffer pool - uint256 amount = pufferProtocol.getSmoothingCommitment(12); - - assertEq(0, pufferProtocol.TREASURY().balance, "zero treasury"); - assertEq(0, address(pufferProtocol.GUARDIAN_MODULE()).balance, "zero guardians"); - assertEq(0, address(pufferProtocol.POOL()).balance, "zero pool"); - - // We don't have additional validations on if the validator is active or not - pufferProtocol.extendCommitment{ value: amount }(NO_RESTAKING, 0, 12); - - assertEq(2280296714778796, pufferProtocol.TREASURY().balance, "non zero treasury"); - assertEq(570074178694699, address(pufferProtocol.GUARDIAN_MODULE()).balance, "non zero guardians"); - assertEq(111164464845466315, address(pufferProtocol.WITHDRAWAL_POOL()).balance, "non zero withdrawal pool"); - assertEq(0, address(pufferProtocol.POOL()).balance, "zero puffer pool"); + function test_change_module_to_custom_module() public { + pufferProtocol.changeModule(bytes32("RANDOM_MODULE"), PufferModule(payable(address(5)))); + address moduleAfterChange = pufferProtocol.getModuleAddress("RANDOM_MODULE"); + assertTrue(address(0) != moduleAfterChange, "module did not change"); } - function testFeeCalculationsEverythingToPufferPool() public { - // Set withdrawal fee to 100% - pufferProtocol.setWithdrawalPoolRate(0); + // function testRegisterOneValidator() public { + // // Start balance of the PufferVault + // uint256 startBalance = 1000 ether; - // Default values are - // 2% guardians - // 0.5% guardians - // 0% to the withdrawal pool - // 100% to puffer pool - uint256 amount = pufferProtocol.getSmoothingCommitment(12); + // assertEq(pufferVault.totalAssets(), startBalance, "it should start with 1000 eth"); + // assertEq(pufferVault.balanceOf(LIQUIDITY_PROVIDER), startBalance, "the LP got all the pufETH"); + // assertEq(pufferVault.maxWithdraw(LIQUIDITY_PROVIDER), startBalance, "lp can withdraw everything"); - assertEq(0, pufferProtocol.TREASURY().balance, "zero treasury"); - assertEq(0, address(pufferProtocol.GUARDIAN_MODULE()).balance, "zero guardians"); - assertEq(0, address(pufferProtocol.POOL()).balance, "zero pool"); + // // Register 1 validator + // _registerValidatorKey(bytes32("alice"), NO_RESTAKING); - // We don't have additional validations on if the validator is active or not - pufferProtocol.extendCommitment{ value: amount }(NO_RESTAKING, 0, 12); + // uint256 smoothingCommitment = pufferOracle.getValidatorTicketPrice(); + // uint256 bond = 1 ether; - assertEq(2280296714778796, pufferProtocol.TREASURY().balance, "non zero treasury"); - assertEq(570074178694699, address(pufferProtocol.GUARDIAN_MODULE()).balance, "non zero guardians"); - assertEq(0, address(pufferProtocol.WITHDRAWAL_POOL()).balance, "non zero withdrawal pool"); - assertEq(111164464845466315, address(pufferProtocol.POOL()).balance, "zero puffer pool"); - } + // uint256 treasuryFee = + // FixedPointMathLib.fullMulDiv(smoothingCommitment, pufferProtocol.getProtocolFeeRate(), 100 * 1e18); + // uint256 guardiansFee = + // FixedPointMathLib.fullMulDiv(smoothingCommitment, pufferProtocol.getGuardiansFeeRate(), 100 * 1e18); - function testChangeModule() public { - vm.expectRevert(IPufferProtocol.InvalidPufferModule.selector); - pufferProtocol.changeModule(NO_RESTAKING, PufferModule(payable(address(5)))); - } + // // Amount of rewards for pufETH holders (SC - fees) + // assertEq(smoothingCommitment - treasuryFee - guardiansFee, 116960846822092934, "rewards"); // ~0.11 ETH - function testChangeModuleToCustom() public { - pufferProtocol.changeModule(bytes32("RANDOM_MODULE"), PufferModule(payable(address(5)))); - address moduleAfterChange = pufferProtocol.getModuleAddress("RANDOM_MODULE"); - assertTrue(address(0) != moduleAfterChange, "module did not change"); - } + // uint256 expectedAmount = startBalance + smoothingCommitment + bond - treasuryFee - guardiansFee; + // assertEq(pufferVault.totalAssets(), expectedAmount, "vault balance after deposit"); - function _registerValidatorKey(bytes32 pubKeyPart, bytes32 moduleName) internal { - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); + // assertEq(pufferVault.balanceOf(address(pufferProtocol)), 1 ether, "protocol should have 1 pufETH"); - bytes memory pubKey = _getPubKey(pubKeyPart); + // assertGt( + // pufferVault.maxWithdraw(LIQUIDITY_PROVIDER), + // startBalance, + // "lp can withdraw more than it originally deposited" + // ); - ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, moduleName); - - uint256 idx = pufferProtocol.getPendingValidatorIndex(moduleName); + // assertGt(pufferVault.maxWithdraw(address(pufferProtocol)), 1 ether, "pufETH in protocol appreciated"); - uint256 bond = 1 ether; + // vm.startPrank(LIQUIDITY_PROVIDER); + // uint256 ethWithdrawn = pufferVault.withdraw( + // pufferVault.maxWithdraw(address(LIQUIDITY_PROVIDER)), LIQUIDITY_PROVIDER, LIQUIDITY_PROVIDER + // ); - vm.expectEmit(true, true, true, true); - emit ValidatorKeyRegistered(pubKey, idx, moduleName, true); - pufferProtocol.registerValidatorKey{ value: (smoothingCommitment + bond) }(validatorKeyData, moduleName, 1); - } + // vm.startPrank(address(pufferProtocol)); + // pufferVault.withdraw( + // pufferVault.maxWithdraw(address(pufferProtocol)), makeAddr("puffer_recipient"), address(pufferProtocol) + // ); - function testStopRegistration() public { - vm.deal(address(pool), 100 ether); + // // 1 wei is left because of rounding + // assertEq(pufferVault.totalAssets(), 1, "everything is gone"); + // } + function test_stop_registration() public { + // Register two validators _registerValidatorKey(bytes32("alice"), NO_RESTAKING); _registerValidatorKey(bytes32("bob"), NO_RESTAKING); - assertEq(pool.balanceOf(address(pufferProtocol)), 2 ether, "pool should have the bond amount for 2 validators"); + assertApproxEqRel( + pufferVault.maxWithdraw(address(pufferProtocol)), + 2 ether, + pointZeroOne, + "pool should have the bond amount for 2 validators" + ); vm.prank(address(4123123)); // random sender vm.expectRevert(Unauthorized.selector); - pufferProtocol.stopRegistration(NO_RESTAKING, 0); + pufferProtocol.cancelRegistration(NO_RESTAKING, 0); (bytes32 moduleName, uint256 idx) = pufferProtocol.getNextValidatorToProvision(); @@ -578,13 +512,23 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit ValidatorDequeued(alicePubKey, 0); - pufferProtocol.stopRegistration(NO_RESTAKING, 0); + pufferProtocol.cancelRegistration(NO_RESTAKING, 0); assertEq(pufferProtocol.getNextValidatorToBeProvisionedIndex(NO_RESTAKING), 1, "1 index is next in line"); - assertEq(pool.balanceOf(address(pufferProtocol)), 1 ether, "pool should have the bond amount for 1 validators"); + assertApproxEqRel( + pufferVault.maxWithdraw(address(pufferProtocol)), + 1 ether, + pointZeroOne, + "pool should have the bond amount for 1 validators" + ); // Because this contract is msg.sender, it means that it is the node operator - assertEq(pool.balanceOf(address(this)), 1 ether, "node operator should get 1 pufETH for Alice"); + assertApproxEqRel( + pufferVault.maxWithdraw(address(this)), + 1 ether, + pointZeroOne, + "node operator should get ~1 pufETH for Alice" + ); (moduleName, idx) = pufferProtocol.getNextValidatorToProvision(); @@ -595,20 +539,20 @@ contract PufferProtocolTest is TestHelper { // Unauthorized, because the protocol is expecting signature for bob vm.expectRevert(Unauthorized.selector); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); // Bob should be provisioned next - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("bob")))); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("bob"))), 0); // Invalid status vm.expectRevert(abi.encodeWithSelector(IPufferProtocol.InvalidValidatorState.selector, Status.DEQUEUED)); - pufferProtocol.stopRegistration(NO_RESTAKING, 0); + pufferProtocol.cancelRegistration(NO_RESTAKING, 0); } - function testRegisterMultipleValidatorKeysAndDequeue(bytes32 alicePubKeyPart, bytes32 bobPubKeyPart) public { + function test_register_multiple_validator_keys_and_dequeue(bytes32 alicePubKeyPart, bytes32 bobPubKeyPart) public { address bob = makeAddr("bob"); vm.deal(bob, 10 ether); - address alice = makeAddr("alice"); + vm.deal(alice, 10 ether); bytes memory bobPubKey = _getPubKey(bobPubKeyPart); @@ -638,21 +582,19 @@ contract PufferProtocolTest is TestHelper { assertEq(pufferProtocol.getPendingValidatorIndex(NO_RESTAKING), 5, "next pending validator index"); - vm.deal(address(pool), 1000 ether); - bytes[] memory signatures = _getGuardianSignatures(zeroPubKey); // // 1. provision zero key vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(zeroPubKey, 0, NO_RESTAKING); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); bytes[] memory bobSignatures = _getGuardianSignatures(bobPubKey); // Provision Bob that is not zero pubKey vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(bobPubKey, 1, NO_RESTAKING); - pufferProtocol.provisionNode(bobSignatures); + pufferProtocol.provisionNode(bobSignatures, 0); Validator memory bobValidator = pufferProtocol.getValidatorInfo(NO_RESTAKING, 1); @@ -663,7 +605,7 @@ contract PufferProtocolTest is TestHelper { signatures = _getGuardianSignatures(zeroPubKey); emit SuccessfullyProvisioned(zeroPubKey, 3, NO_RESTAKING); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); // Get validators Validator[] memory registeredValidators = pufferProtocol.getValidators(NO_RESTAKING); @@ -675,7 +617,7 @@ contract PufferProtocolTest is TestHelper { assertEq(registeredValidators[4].node, address(this), "this contract should should be the fifth one"); } - function testProvisionNode() public { + function test_provision_node() public { pufferProtocol.createPufferModule(EIGEN_DA, "", address(0)); pufferProtocol.createPufferModule(CRAZY_GAINS, "", address(0)); @@ -692,7 +634,7 @@ contract PufferProtocolTest is TestHelper { emit ModuleWeightsChanged(oldWeights, newWeights); pufferProtocol.setModuleWeights(newWeights); - vm.deal(address(pool), 10000 ether); + vm.deal(address(pufferVault), 10000 ether); _registerValidatorKey(bytes32("bob"), NO_RESTAKING); _registerValidatorKey(bytes32("alice"), NO_RESTAKING); @@ -712,7 +654,7 @@ contract PufferProtocolTest is TestHelper { // Provision Bob that is not zero pubKey vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("bob")), 0, NO_RESTAKING); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -724,7 +666,7 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("benjamin")), 0, EIGEN_DA); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -746,12 +688,12 @@ contract PufferProtocolTest is TestHelper { // Provisioning of rocky should fail, because jason is next in line signatures = _getGuardianSignatures(_getPubKey(bytes32("rocky"))); vm.expectRevert(Unauthorized.selector); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); signatures = _getGuardianSignatures(_getPubKey(bytes32("jason"))); // Provision Jason - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -760,7 +702,7 @@ contract PufferProtocolTest is TestHelper { // Rocky is now in line assertTrue(nextModule == CRAZY_GAINS, "module selection"); assertTrue(nextId == 0, "module id"); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -775,32 +717,31 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("alice")), 1, NO_RESTAKING); - pufferProtocol.provisionNode(signatures); + pufferProtocol.provisionNode(signatures, 0); } - function testCreatePufferModule() public { + function test_create_puffer_module() public { bytes32 name = bytes32("LEVERAGED_RESTAKING"); pufferProtocol.createPufferModule(name, "", address(0)); IPufferModule module = IPufferModule(pufferProtocol.getModuleAddress(name)); assertEq(module.NAME(), name, "names"); } - function testClaimBackBond() public { + function test_claim_bond() public { // In our test case, we are posting roots and simulating a full withdrawal before the validator registration _setupMerkleRoot(); // For us to test the withdrawal from the node operator, we must register and provision that validator // In our case we have 2 validators NO_RESTAKING and EIGEN_DA - address alice = makeAddr("alice"); vm.deal(alice, 5 ether); address bob = makeAddr("bob"); vm.deal(bob, 5 ether); address charlie = makeAddr("charlie"); vm.deal(charlie, 5 ether); - vm.deal(address(pool), 100 ether); + vm.deal(address(pufferVault), 100 ether); - assertEq(pool.balanceOf(address(pufferProtocol)), 0, "0 pufETH in protocol"); + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "0 pufETH in protocol"); // Create validators vm.startPrank(alice); @@ -811,12 +752,14 @@ contract PufferProtocolTest is TestHelper { _registerValidatorKey(bytes32("charlie"), NO_RESTAKING); // PufferProtocol should hold pufETH (bond for 3 validators) - assertEq(pool.balanceOf(address(pufferProtocol)), 3 ether, "3 pufETH in protocol"); + assertGt( + (pufferVault.maxWithdraw(address(pufferProtocol))), 3 ether, "> 3 worth of ETH in pufETH in the protocol" + ); // Provision validators - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice")))); - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("bob")))); - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("charlie")))); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 0); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("bob"))), 0); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("charlie"))), 0); bytes32[] memory aliceProof = fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 0); @@ -824,74 +767,82 @@ contract PufferProtocolTest is TestHelper { vm.startPrank(alice); // Invalid block number = invalid proof vm.expectRevert(abi.encodeWithSelector(IPufferProtocol.InvalidMerkleProof.selector)); - pufferProtocol.stopValidator({ + + StoppedValidatorInfo memory validatorInfo = StoppedValidatorInfo({ moduleName: NO_RESTAKING, validatorIndex: 0, blockNumber: 150, withdrawalAmount: 32 ether, wasSlashed: false, - merkleProof: aliceProof + validatorStopTimestamp: block.timestamp }); - assertEq(pool.balanceOf(alice), 0, "alice has zero pufETH"); + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: aliceProof }); - // Valid proof - pufferProtocol.stopValidator({ + assertEq(pufferVault.balanceOf(alice), 0, "alice has zero pufETH"); + + validatorInfo = StoppedValidatorInfo({ moduleName: NO_RESTAKING, validatorIndex: 0, blockNumber: 100, withdrawalAmount: 32 ether, wasSlashed: false, - merkleProof: aliceProof + validatorStopTimestamp: block.timestamp }); + // Valid proof + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: aliceProof }); + // Try again, now the validator will be in invalid state vm.expectRevert(abi.encodeWithSelector(IPufferProtocol.InvalidValidatorState.selector, Status.EXITED)); - pufferProtocol.stopValidator({ - moduleName: NO_RESTAKING, - validatorIndex: 0, - blockNumber: 100, - withdrawalAmount: 32 ether, - wasSlashed: false, - merkleProof: aliceProof - }); + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: aliceProof }); // Alice receives the bond + the reward - assertEq(pool.balanceOf(alice), 1 ether, "alice received back the bond in pufETH"); + assertGt( + pufferVault.maxWithdraw(alice), + 1 ether, + "alice received back the bond in pufETH which is worth more than she deposited" + ); bytes32[] memory bobProof = fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 1); - assertEq(pool.balanceOf(bob), 0, "bob has zero pufETH"); + assertEq(pufferVault.balanceOf(bob), 0, "bob has zero pufETH"); - pufferProtocol.stopValidator({ + // Bob validator info + validatorInfo = StoppedValidatorInfo({ moduleName: EIGEN_DA, validatorIndex: 0, blockNumber: 100, withdrawalAmount: 31 ether, wasSlashed: true, - merkleProof: bobProof + validatorStopTimestamp: block.timestamp }); - assertEq(pool.balanceOf(bob), 0, "bob has zero pufETH after"); + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: bobProof }); + + assertEq(pufferVault.balanceOf(bob), 0, "bob has zero pufETH after"); bytes32[] memory charlieProof = fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 2); - pufferProtocol.stopValidator({ + // Charlie validator info + validatorInfo = StoppedValidatorInfo({ moduleName: NO_RESTAKING, validatorIndex: 1, blockNumber: 100, withdrawalAmount: 31.6 ether, wasSlashed: false, - merkleProof: charlieProof + validatorStopTimestamp: block.timestamp }); - // assertEq(pool.balanceOf(charlie), 0.6 ether, "Charlie has 0.6 pufETH after"); + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: charlieProof }); + + assertGt(pufferVault.maxWithdraw(charlie), 0.6 ether, "Charlie has 0.6 + extra that he earned after"); } // Test smart contract upgradeability (UUPS) - function testUpgrade() public { + function test_upgrade() public { vm.expectRevert(); - uint256 result = PufferProtocolMockUpgrade(payable(address(pool))).returnSomething(); + uint256 result = PufferProtocolMockUpgrade(payable(address(pufferVault))).returnSomething(); PufferProtocolMockUpgrade newImplementation = new PufferProtocolMockUpgrade(address(beacon)); pufferProtocol.upgradeToAndCall(address(newImplementation), ""); @@ -901,20 +852,8 @@ contract PufferProtocolTest is TestHelper { assertEq(result, 1337); } - function testPause() public { - pool.depositETH{ value: 1 ether }(); - - vm.startPrank(_broadcaster); // Admin - // Pause - accessManager.setTargetClosed(address(pool), true); - vm.stopPrank(); - - vm.expectRevert(); - pool.depositETH{ value: 1 ether }(); - } - // Test registering the validator with a huge number of months committed - function testRegisterValidatorWithHugeCommitment() external { + function test_register_validator_with_huge_commitment() external { bytes memory pubKey = _getPubKey(bytes32("alice")); ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, NO_RESTAKING); @@ -923,98 +862,249 @@ contract PufferProtocolTest is TestHelper { uint256 bond = 2 ether; vm.expectRevert(); - pufferProtocol.registerValidatorKey{ value: bond }(validatorKeyData, NO_RESTAKING, type(uint256).max); + pufferProtocol.registerValidatorKey{ value: bond }( + validatorKeyData, NO_RESTAKING, type(uint256).max, emptyPermit, emptyPermit + ); } // Node operator can deposit Bond in pufETH - function testRegisterValidatorKeyWithPermit() external { - address alice = makeAddr("alice"); + function test_register_pufETH_approve_buy_VT() external { bytes memory pubKey = _getPubKey(bytes32("alice")); vm.deal(alice, 10 ether); + uint256 expectedMint = pufferVault.previewDeposit(1 ether); + assertGt(expectedMint, 0, "should expect more pufETH"); + // Alice mints 2 ETH of pufETH vm.startPrank(alice); - pool.depositETH{ value: 2 ether }(); + uint256 minted = pufferVault.depositETH{ value: 1 ether }(alice); + assertGt(minted, 0, "should mint pufETH"); // approve pufETH to pufferProtocol - pool.approve(address(pufferProtocol), type(uint256).max); + pufferVault.approve(address(pufferProtocol), type(uint256).max); - assertEq(pool.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); - assertEq(pool.balanceOf(alice), 2 ether, "2 pufETH before for alice"); + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); + assertEq(pufferVault.balanceOf(alice), 1 ether, "1 pufETH before for alice"); // In this case, the only important data on permit is the amount // Permit call will fail, but the amount is reused ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); Permit memory permit; - permit.amount = pool.balanceOf(alice); + permit.amount = pufferVault.balanceOf(alice); - // Get the smoothing commitment amount for 6 months - uint256 sc = pufferProtocol.getSmoothingCommitment(6); + // Get the smoothing commitment amount for 180 days + uint256 sc = pufferOracle.getValidatorTicketPrice() * 180; // Register validator key by paying SC in ETH and depositing bond in pufETH vm.expectEmit(true, true, true, true); emit ValidatorKeyRegistered(pubKey, 0, NO_RESTAKING, true); - pufferProtocol.registerValidatorKeyPermit{ value: sc }(data, NO_RESTAKING, 6, permit); + pufferProtocol.registerValidatorKey{ value: sc }(data, NO_RESTAKING, 180, permit, emptyPermit); - assertEq(pool.balanceOf(alice), 0, "0 pufETH after for alice"); - assertEq(pool.balanceOf(address(pufferProtocol)), 2 ether, "2 pufETH after"); + assertEq(pufferVault.balanceOf(alice), 0, "0 pufETH after for alice"); + assertApproxEqRel(pufferVault.balanceOf(address(pufferProtocol)), 1 ether, pointZeroZeroTwo, "~1 pufETH after"); } - // Node operator can deposit Bond in pufETH with Permit - function testRegisterValidatorKeyWithPermitSignature() external { - address alice = makeAddr("alice"); + // Node operator can deposit Bond with Permit and pay for the VT in ETH + function test_register_pufETH_permit_pay_VT() external { bytes memory pubKey = _getPubKey(bytes32("alice")); vm.deal(alice, 10 ether); // Alice mints 2 ETH of pufETH vm.startPrank(alice); - pool.depositETH{ value: 2 ether }(); + pufferVault.depositETH{ value: 1 ether }(alice); - assertEq(pool.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); - assertEq(pool.balanceOf(alice), 2 ether, "2 pufETH before for alice"); + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); + assertEq(pufferVault.balanceOf(alice), 1 ether, "1 pufETH before for alice"); ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); // Generate Permit data for 2 pufETH to the protocol - Permit memory permit = _signPermit(_testTemps("alice", address(pufferProtocol), 2 ether, block.timestamp)); + Permit memory permit = _signPermit( + _testTemps("alice", address(pufferProtocol), 2 ether, block.timestamp), pufferVault.DOMAIN_SEPARATOR() + ); + uint256 numberOfDays = 180; // Get the smoothing commitment amount for 6 months - uint256 sc = pufferProtocol.getSmoothingCommitment(6); + uint256 sc = pufferOracle.getValidatorTicketPrice() * numberOfDays; // Register validator key by paying SC in ETH and depositing bond in pufETH vm.expectEmit(true, true, true, true); emit ValidatorKeyRegistered(pubKey, 0, NO_RESTAKING, true); - pufferProtocol.registerValidatorKeyPermit{ value: sc }(data, NO_RESTAKING, 6, permit); + pufferProtocol.registerValidatorKey{ value: sc }(data, NO_RESTAKING, numberOfDays, permit, emptyPermit); - assertEq(pool.balanceOf(alice), 0, "0 pufETH after for alice"); - assertEq(pool.balanceOf(address(pufferProtocol)), 2 ether, "2 pufETH after"); + assertEq(pufferVault.balanceOf(alice), 0, "0 pufETH after for alice"); + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 1 ether, "1 pufETH after"); } - // Node operator can deposit Bond in pufETH - function testRegisterValidatorKeyWithPermitSignatureRevertsInvalidSC() external { - address alice = makeAddr("alice"); + // Node operator can deposit both VT and pufETH with Permit + function test_register_both_permit() external { bytes memory pubKey = _getPubKey(bytes32("alice")); vm.deal(alice, 10 ether); + uint256 numberOfDays = 200; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + // Alice mints 2 ETH of pufETH vm.startPrank(alice); - pool.depositETH{ value: 2 ether }(); + // Purchase pufETH + pufferVault.depositETH{ value: 1 ether }(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + // Because Alice purchased a lot of VT's, it changed the conversion rate + // Because of that the registerValidatorKey will .transferFrom a smaller amount of pufETH + uint256 leftOverPufETH = pufferVault.balanceOf(alice) - pufferVault.convertToShares(1 ether); + + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); + assertEq(pufferVault.balanceOf(alice), 1 ether, "1 pufETH before for alice"); + assertEq(validatorTicket.balanceOf(alice), _upscaleTo18Decimals(numberOfDays), "VT before for alice"); + + ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); + + uint256 bond = 1 ether; + Permit memory pufETHPermit = _signPermit( + _testTemps("alice", address(pufferProtocol), bond, block.timestamp), pufferVault.DOMAIN_SEPARATOR() + ); + Permit memory vtPermit = _signPermit( + _testTemps("alice", address(pufferProtocol), _upscaleTo18Decimals(amount), block.timestamp), + validatorTicket.DOMAIN_SEPARATOR() + ); + + vm.expectEmit(true, true, true, true); + emit ValidatorKeyRegistered(pubKey, 0, NO_RESTAKING, true); + pufferProtocol.registerValidatorKey(data, NO_RESTAKING, numberOfDays, pufETHPermit, vtPermit); + + assertEq(pufferVault.balanceOf(alice), leftOverPufETH, "alice should have some leftover pufETH"); + assertEq(validatorTicket.balanceOf(alice), 0, "0 vt after for alice"); + assertApproxEqRel(pufferVault.balanceOf(address(pufferProtocol)), bond, 0.002e18, "1 pufETH after"); + } + + // Node operator can deposit both VT and pufETH with .approve + function test_register_both_approve() external { + bytes memory pubKey = _getPubKey(bytes32("alice")); + vm.deal(alice, 10 ether); + + uint256 numberOfDays = 200; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + // Alice mints 2 ETH of pufETH + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + // Purchase pufETH + pufferVault.depositETH{ value: 1 ether }(alice); + + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); + // 1 wei diff + assertApproxEqAbs( + pufferVault.previewRedeem(pufferVault.balanceOf(alice)), 1 ether, 1, "1 pufETH before for alice" + ); + assertEq(validatorTicket.balanceOf(alice), _upscaleTo18Decimals(numberOfDays), "VT before for alice"); + + ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); + + uint256 bond = 1 ether; + + pufferVault.approve(address(pufferProtocol), type(uint256).max); + validatorTicket.approve(address(pufferProtocol), type(uint256).max); + + Permit memory vtPermit = emptyPermit; + vtPermit.amount = _upscaleTo18Decimals(amount); // upscale to 18 decimals + + Permit memory pufETHPermit = emptyPermit; + pufETHPermit.amount = pufferVault.convertToShares(bond); + + vm.expectEmit(true, true, true, true); + emit ValidatorKeyRegistered(pubKey, 0, NO_RESTAKING, true); + pufferProtocol.registerValidatorKey(data, NO_RESTAKING, numberOfDays, pufETHPermit, vtPermit); + + assertEq(pufferVault.balanceOf(alice), 0, "0 pufETH after for alice"); + assertEq(validatorTicket.balanceOf(alice), 0, "0 vt after for alice"); + // 1 wei diff + assertApproxEqAbs( + pufferVault.previewRedeem(pufferVault.balanceOf(address(pufferProtocol))), bond, 1, "1 pufETH after" + ); + } + + // Node operator can pay for pufETH with ETH and use Permit for VT + function test_register_pufETH_pay_vt_approve() external { + bytes memory pubKey = _getPubKey(bytes32("alice")); + vm.deal(alice, 10 ether); + + uint256 numberOfDays = 30; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + // Alice mints 2 ETH of pufETH + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + assertEq(pufferVault.balanceOf(address(pufferProtocol)), 0, "zero pufETH before"); + assertEq(pufferVault.balanceOf(alice), 0, "0 pufETH before for alice"); + assertEq(validatorTicket.balanceOf(alice), _upscaleTo18Decimals(numberOfDays), "VT before for alice"); + + ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); + // Generate Permit data for 2 pufETH to the protocol + Permit memory permit = _signPermit( + _testTemps("alice", address(pufferProtocol), _upscaleTo18Decimals(amount), block.timestamp), + validatorTicket.DOMAIN_SEPARATOR() + ); + + // Alice is using SGX + uint256 bond = 1 ether; + + vm.expectEmit(true, true, true, true); + emit ValidatorKeyRegistered(pubKey, 0, NO_RESTAKING, true); + pufferProtocol.registerValidatorKey{ value: bond }(data, NO_RESTAKING, numberOfDays, emptyPermit, permit); + + assertEq(pufferVault.balanceOf(alice), 0, "0 pufETH after for alice"); + assertApproxEqRel(pufferVault.balanceOf(address(pufferProtocol)), 1 ether, pointZeroFive, "~1 pufETH after"); + } + + // Node operator can deposit Bond in pufETH + function test_register_validator_key_with_permit_reverts_invalid_vt_amount() external { + bytes memory pubKey = _getPubKey(bytes32("alice")); + vm.deal(alice, 100 ether); + + // Alice mints 2 ETH of pufETH + vm.startPrank(alice); + pufferVault.depositETH{ value: 2 ether }(alice); ValidatorKeyData memory data = _getMockValidatorKeyData(pubKey, NO_RESTAKING); // Generate Permit data for 10 pufETH to the protocol - Permit memory permit = _signPermit(_testTemps("alice", address(pufferProtocol), 0.5 ether, block.timestamp)); + Permit memory permit = _signPermit( + _testTemps("alice", address(pufferProtocol), 0.5 ether, block.timestamp), pufferVault.DOMAIN_SEPARATOR() + ); - // Try to pay invalid SC amount - vm.expectRevert(IPufferProtocol.InvalidETHAmount.selector); - pufferProtocol.registerValidatorKeyPermit{ value: 0.1 ether }(data, NO_RESTAKING, 6, permit); + // Underpay VT + vm.expectRevert(); + pufferProtocol.registerValidatorKey{ value: 0.1 ether }(data, NO_RESTAKING, 60, permit, emptyPermit); - uint256 sc = pufferProtocol.getSmoothingCommitment(6); + uint256 vtPrice = pufferOracle.getValidatorTicketPrice(); - // Try to pay good amount for SC, but not enough pufETH for the bond + // Overpay VT vm.expectRevert(IPufferProtocol.InvalidETHAmount.selector); - pufferProtocol.registerValidatorKeyPermit{ value: sc }(data, NO_RESTAKING, 6, permit); + pufferProtocol.registerValidatorKey{ value: 5 ether }(data, NO_RESTAKING, 60, permit, emptyPermit); } - function testValidatorLimitPerModule() external { + function test_validator_griefing_attack() external { + vm.deal(address(pufferVault), 100 ether); + + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + bytes[] memory guardianSignatures = _getGuardianSignatures(_getPubKey(bytes32("alice"))); + // Register and provision Alice + // Alice may be an active validator or it can be exited, doesn't matter + pufferProtocol.provisionNode(guardianSignatures, 0); + + // Register another validator with using the same data + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + // Try to provision it with the original message (replay attack) + // It should revert + vm.expectRevert(Unauthorized.selector); + pufferProtocol.provisionNode(guardianSignatures, 0); + } + + function test_validator_limit_per_module() external { _registerValidatorKey(bytes32("alice"), NO_RESTAKING); vm.expectEmit(true, true, true, true); @@ -1022,55 +1112,580 @@ contract PufferProtocolTest is TestHelper { pufferProtocol.setValidatorLimitPerModule(NO_RESTAKING, 1); // Revert if the registration will be over the limit - uint256 smoothingCommitment = pufferProtocol.getSmoothingCommitment(1); + uint256 smoothingCommitment = pufferOracle.getValidatorTicketPrice(); bytes memory pubKey = _getPubKey(bytes32("bob")); ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, NO_RESTAKING); uint256 bond = 1 ether; vm.expectRevert(IPufferProtocol.ValidatorLimitForModuleReached.selector); - pufferProtocol.registerValidatorKey{ value: (smoothingCommitment + bond) }(validatorKeyData, NO_RESTAKING, 1); + pufferProtocol.registerValidatorKey{ value: (smoothingCommitment + bond) }( + validatorKeyData, NO_RESTAKING, 30, emptyPermit, emptyPermit + ); } - function testClaimBackBondForSingleWithdrawal() external { + function test_claim_bond_for_single_withdrawal() external { _singleWithdrawalMerkleRoot(); - address alice = makeAddr("alice"); vm.deal(alice, 2 ether); - vm.deal(address(pool), 32 ether); vm.startPrank(alice); _registerValidatorKey(bytes32("alice"), NO_RESTAKING); - // PufferProtocol should hold pufETH (bond for 1 validators) - assertEq(pool.balanceOf(address(pufferProtocol)), 1 ether, "1 pufETH in protocol"); + // 1 wei diff + assertApproxEqAbs( + pufferVault.previewRedeem(pufferVault.balanceOf(address(pufferProtocol))), + 1 ether, + 1, + "~1 pufETH in protocol" + ); + assertApproxEqAbs( + pufferVault.maxWithdraw(address(pufferProtocol)), 1 ether, 1, "~1 pufETH in protocol maxRedeem" + ); + + Validator memory validator = pufferProtocol.getValidatorInfo(NO_RESTAKING, 0); + + assertEq(validator.bond, pufferVault.balanceOf(address(pufferProtocol)), "alice bond is in the protocol"); + + uint256 startTimestamp = 1707411226; + + vm.warp(startTimestamp); // Provision validators - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice")))); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); - assertEq(pool.balanceOf(alice), 0, "alice has zero pufETH"); + assertEq(pufferVault.balanceOf(alice), 0, "alice has zero pufETH"); - // Valid proof - pufferProtocol.stopValidator({ + // 15 days later + vm.warp(startTimestamp + 16 days); + + StoppedValidatorInfo memory validatorInfo = StoppedValidatorInfo({ moduleName: NO_RESTAKING, validatorIndex: 0, blockNumber: 200, withdrawalAmount: 32 ether, wasSlashed: false, + validatorStopTimestamp: (startTimestamp + 16 days) + }); + + // Valid proof + pufferProtocol.retrieveBond({ + validatorInfo: validatorInfo, merkleProof: fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 0) }); + // Alice got the pufETH + assertGt(pufferVault.balanceOf(alice), 0.9 ether, "alice got the pufETH"); + assertApproxEqAbs(pufferVault.maxWithdraw(alice), 1 ether, 1, "max redeem for alice"); + assertApproxEqAbs( + pufferVault.previewRedeem(pufferVault.balanceOf(address(alice))), 1 ether, 1, "alice got back ~1 eth" + ); + bytes32[] memory proof2 = fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 1); // Valid proof for the same validator will revert vm.expectRevert(); - pufferProtocol.stopValidator({ + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: proof2 }); + + // Alice doesn't withdraw her VT's right away + vm.warp(startTimestamp + 50 days); + + // After 50 days she should have 15 VT + uint256 vtsLeft = pufferProtocol.getValidatorTicketsBalance(alice); + assertApproxEqRel(vtsLeft, 15 ether, pointZeroZeroOne, "alice has 15 VTs left"); + } + + // Alice deposits VT for herself + function test_deposit_validator_tickets_approval() public { + vm.deal(alice, 10 ether); + + uint256 numberOfDays = 200; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + assertEq(validatorTicket.balanceOf(alice), 200 ether, "alice got 200 VT"); + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 0, "protocol got 0 VT"); + + Permit memory vtPermit = emptyPermit; + vtPermit.amount = 200 ether; + + // Approve VT + validatorTicket.approve(address(pufferProtocol), 2000 ether); + + // Deposit for herself + vm.expectEmit(true, true, true, true); + emit IPufferProtocol.ValidatorTicketsDeposited(alice, alice, 200 ether); + pufferProtocol.depositValidatorTickets(vtPermit, alice); + + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 200 ether, "protocol got 200 VT"); + assertEq(validatorTicket.balanceOf(address(alice)), 0, "alice got 0"); + } + + // Alice double deposit VT + function test_double_deposit_validator_tickets_approval() public { + vm.deal(alice, 1000 ether); + + uint256 numberOfDays = 1000; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + assertEq(validatorTicket.balanceOf(alice), 1000 ether, "alice got 1000 VT"); + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 0, "protocol got 0 VT"); + + Permit memory vtPermit = emptyPermit; + vtPermit.amount = 200 ether; + + // Approve VT + validatorTicket.approve(address(pufferProtocol), 2000 ether); + + // Deposit for herself + vm.expectEmit(true, true, true, true); + emit IPufferProtocol.ValidatorTicketsDeposited(alice, alice, 200 ether); + pufferProtocol.depositValidatorTickets(vtPermit, alice); + + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 200 ether, "protocol got 200 VT"); + assertEq(validatorTicket.balanceOf(address(alice)), 800 ether, "alice got 800"); + assertEq(pufferProtocol.getValidatorTicketsBalance(alice), 200 ether, "alice got 200 VT in the protocol"); + + // Perform a second deposit of 800 VT + vtPermit.amount = 800 ether; + pufferProtocol.depositValidatorTickets((vtPermit), alice); + assertEq( + pufferProtocol.getValidatorTicketsBalance(alice), 1000 ether, "alice should have 1000 vt in the protocol" + ); + } + + // Alice deposits VT for bob + function test_deposit_validator_tickets_permit_for_bob() public { + vm.deal(alice, 10 ether); + + uint256 numberOfDays = 200; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + assertEq(validatorTicket.balanceOf(alice), 200 ether, "alice got 200 VT"); + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 0, "protocol got 0 VT"); + + // Sign the permit + Permit memory vtPermit = _signPermit( + _testTemps("alice", address(pufferProtocol), _upscaleTo18Decimals(numberOfDays), block.timestamp), + validatorTicket.DOMAIN_SEPARATOR() + ); + + address bob = makeAddr("bob"); + + // Deposit for Bob + vm.expectEmit(true, true, true, true); + emit IPufferProtocol.ValidatorTicketsDeposited(bob, alice, 200 ether); + pufferProtocol.depositValidatorTickets(vtPermit, bob); + + assertEq(pufferProtocol.getValidatorTicketsBalance(bob), 200 ether, "bob got the VTS in the protocol"); + assertEq(pufferProtocol.getValidatorTicketsBalance(alice), 0, "alice got no VTS in the protocol"); + } + + // Alice double deposit VT for Bob + function test_double_deposit_validator_tickets_permit_for_bob() public { + vm.deal(alice, 1000 ether); + + uint256 numberOfDays = 1000; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + vm.startPrank(alice); + // Alice purchases VT + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + + assertEq(validatorTicket.balanceOf(alice), 1000 ether, "alice got 1000 VT"); + assertEq(validatorTicket.balanceOf(address(pufferProtocol)), 0, "protocol got 0 VT"); + + // Sign the permit + Permit memory vtPermit = _signPermit( + _testTemps("alice", address(pufferProtocol), _upscaleTo18Decimals(200), block.timestamp), + validatorTicket.DOMAIN_SEPARATOR() + ); + + address bob = makeAddr("bob"); + + // Deposit for Bob + vm.expectEmit(true, true, true, true); + emit IPufferProtocol.ValidatorTicketsDeposited(bob, alice, 200 ether); + pufferProtocol.depositValidatorTickets(vtPermit, bob); + + assertEq(pufferProtocol.getValidatorTicketsBalance(bob), 200 ether, "bob got the VTS in the protocol"); + assertEq(pufferProtocol.getValidatorTicketsBalance(alice), 0, "alice got no VTS in the protocol"); + assertEq(validatorTicket.balanceOf(alice), 800 ether, "Alice still has 800 VTs left in wallet"); + + vm.startPrank(alice); + // Deposit for Bob again + Permit memory vtPermit2 = _signPermit( + _testTemps("alice", address(pufferProtocol), _upscaleTo18Decimals(800), block.timestamp + 1000), + validatorTicket.DOMAIN_SEPARATOR() + ); + validatorTicket.approve(address(pufferProtocol), 800 ether); + pufferProtocol.depositValidatorTickets(vtPermit2, bob); + + assertEq(pufferProtocol.getValidatorTicketsBalance(bob), 1000 ether, "bob got the VTS in the protocol"); + assertEq(pufferProtocol.getValidatorTicketsBalance(alice), 0, "alice got no VTS in the protocol"); + assertEq(validatorTicket.balanceOf(alice), 0, "Alice has no more VTs"); + } + + function test_changeMinimumVTAmount() public { + assertEq(pufferProtocol.getMinimumVtAmount(), 28 ether, "initial value"); + + vm.startPrank(DAO); + pufferProtocol.changeMinimumVTAmount(50 ether); + + assertEq(pufferProtocol.getMinimumVtAmount(), 50 ether, "value after change"); + } + + function test_vt_balance_single_validator() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + // Register Validator key registers validator with 30 VTs + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + // Alice registered validator on block.timestamp = 1 + uint256 balance = pufferProtocol.getValidatorTicketsBalance(alice); + + uint256 startTimestamp = 1707411226; + + // advance block timestamp + vm.warp(startTimestamp); + + // Alice has 30 VTs because her validator is not yet provisioned + assertEq(balance, 30 ether, "alice should have 30 VTs locked in the protocol"); + + // The wait queue is 1 days, the guardians provision the validator with 1 day offset + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + + // We offset the timestamp to + 1 days, Alice should still have 30 VT (because the validating is not live yet) + vm.warp(startTimestamp + 1 days); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), + 30 ether, + pointZeroZeroOne, + "alice should still have ~ 30 because VTS" + ); + + // + 1 day offset + 1 day validating + vm.warp(startTimestamp + 2 days); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 29 ether, pointZeroZeroOne, "alice should have ~29 VTS" + ); + + // 1 days for the validator start + 20 days + vm.warp(startTimestamp + 21 days); // 20 days in seconds + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 10 ether, pointZeroZeroOne, "alice should have ~10 VTS" + ); + } + + function test_vt_balance_different_provision_time() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + // Register Validator key registers validator with 30 VTs + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + // Alice registered validator on block.timestamp = 1 + uint256 balance = pufferProtocol.getValidatorTicketsBalance(alice); + + uint256 startTimestamp = 1707411226; + + // advance block timestamp + vm.warp(startTimestamp); + + // Alice has 90 VTs because her validator is not yet provisioned + assertEq(balance, 90 ether, "alice should have 90 VTs locked in the protocol"); + + // The wait queue is 3 days, the guardians provision the validator with 3 days offset, we credit alice 3 virtual VT's + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 3 ether); + + // We offset the timestamp to + 2 days, Alice should still have 90 VT (because the validating is not live yet) + vm.warp(startTimestamp + 2 days); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), + 91 ether, + pointZeroZeroOne, + "alice should still have ~ 91 VTS (+1 for offset)" + ); + + // At t + 2 days the new validator gets provisioned with 2 days queue + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 2 ether); + + // 90 is the original deposit, +3 virtual for the first validator, but this is t+2 days, so it is +1, and +2 for this valdiator + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), + 93 ether, + pointZeroZeroOne, + "alice should still have ~ 93 VTS" + ); + + // + 3 day offset + 1 day validating + vm.warp(startTimestamp + 4 days); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 89 ether, pointZeroZeroOne, "alice should have ~89 VTS" + ); + + // Validator 1 - 11 days of validating + // Validator 2 - 10 days of validating (provisioning happened T+2 with 2 days offset) + vm.warp(startTimestamp + 14 days); // 20 days in seconds + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 69 ether, pointZeroZeroOne, "alice should have ~69 VTS" + ); + } + + // Alice tries to withdraw all VT before provisioning + function test_withdraw_vt_before_provisioning() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + + // Register Validator key registers validator with 30 VTs + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + // Alice tries to withdraw 28 VTs, but the tx is reverted + + // Revert saying that at least 28 VT must be left in the protocol + vm.expectRevert( + abi.encodeWithSelector(IPufferProtocol.InvalidValidatorTicketAmount.selector, 30 ether, 28 ether) + ); + pufferProtocol.withdrawValidatorTickets(30 ether, alice); + + address bob = makeAddr("bob"); + + assertEq(validatorTicket.balanceOf(bob), 0, "bob 0 VT"); + + // Alice can withdraw 2 VT to bob + pufferProtocol.withdrawValidatorTickets(2 ether, bob); + + assertEq(validatorTicket.balanceOf(bob), 2 ether, "bob got 2 VT"); + assertEq(validatorTicket.balanceOf(alice), 0, "alice 0 VT"); + assertEq(pufferProtocol.getValidatorTicketsBalance(alice), 28 ether, "alice got 28 VT in the protocol"); + } + + function test_register_skip_provision_withdraw_vt() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 30 ether, pointZeroZeroOne, "alice should have ~30 VTS" + ); + + pufferProtocol.skipProvisioning(NO_RESTAKING, _getGuardianSignaturesForSkipping()); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), + 20 ether, + pointZeroZeroOne, + "alice should have ~20 VTS -10 penalty" + ); + + pufferProtocol.withdrawValidatorTickets(uint96(20 ether), alice); + + assertEq(validatorTicket.balanceOf(alice), 20 ether, "alice got her VT"); + } + + // Alice has two validators, stops one, registers and provisions another one, and after some time claims the bond for the stopped + function test_stop_validator_provision_another_claim_bond_for_the_first() public { + _setupMerkleRoot(); + + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + // Register 2 Validators, 2x30 VT + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + uint256 startFirstValidatorTimestamp = 1707411226; + + vm.warp(startFirstValidatorTimestamp); + + // Provision 2 validators in the same timestamp + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + + vm.warp(startFirstValidatorTimestamp + 5 days); + + // 2 Validators are consuming 2x4 VT's + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 82 ether, pointZeroZeroOne, "alice should have ~ 82 VTS" + ); + + bytes32[] memory aliceProof = fullWithdrawalsMerkleProof.getProof(fullWithdrawalMerkleProofData, 0); + + vm.warp(startFirstValidatorTimestamp + 11 days); + + // 2 Validators are consuming 2x10 VT's + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 70 ether, pointZeroZeroOne, "alice should have ~ 70 VTS" + ); + + // Provision another validator with 1 day offset + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + + // 2 Validators are consuming 2x10 VT's + 1 virtual + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 71 ether, pointZeroZeroOne, "alice should have ~ 71 VTS" + ); + + StoppedValidatorInfo memory validatorInfo = StoppedValidatorInfo({ moduleName: NO_RESTAKING, validatorIndex: 0, - blockNumber: 200, - withdrawalAmount: 0, + blockNumber: 100, + withdrawalAmount: 32 ether, wasSlashed: false, - merkleProof: proof2 + validatorStopTimestamp: startFirstValidatorTimestamp + 6 days }); + + pufferProtocol.retrieveBond({ validatorInfo: validatorInfo, merkleProof: aliceProof }); + + // We are at start + 11 days at the moment + // Validator 1 is still active - spent 10 VT's + // Validator 3 just got provisioned right now with 1 days offset + // Validator 2 exited, and was validating for 5 days + assertApproxEqRel( + validatorTicket.balanceOf(address(pufferProtocol)), 71 ether, pointZeroZeroOne, "real vt balance" + ); + + // Alice should have + 5 VT's because of the validator stop timestamp + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 76 ether, pointZeroZeroOne, "alice should have ~ 76 VTS" + ); + } + + function test_vt_burning() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + uint256 provisionedTimestamp = block.timestamp; + // 5 days VT queue + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 5 ether); + + uint256 numberOfDays = 200; + uint256 amount = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + validatorTicket.purchaseValidatorTicket{ value: amount }(alice); + validatorTicket.approve(address(pufferProtocol), type(uint256).max); + + vm.warp(provisionedTimestamp + 3 days); + // Alice should have + 2 VT's virtual + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 32 ether, pointZeroZeroOne, "alice should have ~ 32 VTS" + ); + + vm.warp(provisionedTimestamp + 5 days); + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 30 ether, pointZeroZeroOne, "alice should have ~ 30 VTS" + ); + + vm.warp(provisionedTimestamp + 10 days); + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 25 ether, pointZeroZeroOne, "alice should have ~ 25 VTS" + ); + + // Real balance did not get burned or adjusted + assertApproxEqRel( + validatorTicket.balanceOf(address(pufferProtocol)), 30 ether, pointZeroZeroOne, "real vt balance" + ); + + Permit memory vtPermit = emptyPermit; + vtPermit.amount = 10 ether; + // deposit 10 VT's + pufferProtocol.depositValidatorTickets(vtPermit, alice); + + // 5 VT's real VT's got burned, 10 deposited + assertApproxEqRel( + validatorTicket.balanceOf(address(pufferProtocol)), 35 ether, pointZeroZeroOne, "real vt balance" + ); + + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 35 ether, pointZeroZeroOne, "alice should have ~ 35 VTS" + ); + } + + function test_vt_balance_multiple_validators() public { + vm.deal(alice, 10 ether); + + vm.startPrank(alice); + // Register 3 Validators, 3x30 VT + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + _registerValidatorKey(bytes32("alice"), NO_RESTAKING); + + // Alice registered validator on block.timestamp = 1 + uint256 balance = pufferProtocol.getValidatorTicketsBalance(alice); + + uint256 startFirstValidatorTimestamp = 1707411226; + + // advance block timestamp + vm.warp(startFirstValidatorTimestamp); + + // Alice has 90 VTs because no validators are provisioned + assertEq(balance, 90 ether, "alice should have 90 VTs locked in the protocol"); + + // The wait queue is 1 days, the guardians provision the validator with 1 day offset + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + + // We offset the timestamp to + 1 days, Alice should still have 90 VT (because the validating is not live yet) + vm.warp(startFirstValidatorTimestamp + 1 days); + + // At this point the Validator 1 is live + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), + 90 ether, + pointZeroZeroOne, + "alice should still have ~ 90 because VTS" + ); + + // Validator 1 + // + 1 day offset + 10 day validating + uint256 newTime = startFirstValidatorTimestamp + 11 days; + vm.warp(newTime); + + // Validator 1 has been active for 10 days + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 80 ether, pointZeroZeroOne, "alice should have ~80 VTS" + ); + + // Now we provision another Validator with 1 days offset + pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(bytes32("alice"))), 1 ether); + + // VT balance should be 80, but because one validator just got provisioned + // +1 is because of the offset, a validator just got provisioned with a wait time of 1 day + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 81 ether, pointZeroZeroOne, "alice should have ~81 VTS" + ); + + // Advance the time to start + 6 days + vm.warp(newTime + 6 days); + + // That means that the Validator 2 is active for 5 days + // Validator 1 active for 16 days + assertApproxEqRel( + pufferProtocol.getValidatorTicketsBalance(alice), 69 ether, pointZeroZeroOne, "alice should have ~69 VTS" + ); } function _getGuardianSignatures(bytes memory pubKey) internal view returns (bytes[] memory) { @@ -1083,6 +1698,8 @@ contract PufferProtocolTest is TestHelper { bytes memory withdrawalCredentials = pufferProtocol.getWithdrawalCredentials(validator.module); bytes32 digest = LibGuardianMessages._getBeaconDepositMessageToBeSigned( + pendingIdx, + 0, pubKey, validator.signature, withdrawalCredentials, @@ -1199,8 +1816,7 @@ contract PufferProtocolTest is TestHelper { bytes32 merkleRoot = _buildMerkleProof(validatorExits); // Assert starting state of the pools - assertEq(address(pool).balance, 0, "starting pool balance"); - assertEq(address(withdrawalPool).balance, 0, "starting withdrawal pool balance"); + assertEq(address(pufferVault).balance, 1000 ether, "starting pool balance"); bytes[] memory signatures = _getGuardianEOASignatures( LibGuardianMessages._getPostFullWithdrawalsRootMessage(merkleRoot, 200, modules, amounts) @@ -1215,9 +1831,7 @@ contract PufferProtocolTest is TestHelper { guardianSignatures: signatures }); - // Default split rate for withdrawal pool is 10% - assertEq(address(withdrawalPool).balance, 3.2 ether, "ending withdrawal pool balance"); - assertEq(address(pool).balance, 28.8 ether, "ending pool balance"); + assertEq(address(pufferVault).balance, 1032 ether, "ending pool balance"); } // Sets the merkle root and makes sure that the funds get split between WithdrawalPool and PufferPool ASAP @@ -1270,10 +1884,6 @@ contract PufferProtocolTest is TestHelper { validatorExits[2] = MerkleProofData({ moduleName: NO_RESTAKING, index: 1, amount: 31.6 ether, wasSlashed: 0 }); bytes32 merkleRoot = _buildMerkleProof(validatorExits); - // Assert starting state of the pools - assertEq(address(pool).balance, 0, "starting pool balance"); - assertEq(address(withdrawalPool).balance, 0, "starting withdrawal pool balance"); - bytes[] memory signatures = _getGuardianEOASignatures( LibGuardianMessages._getPostFullWithdrawalsRootMessage(merkleRoot, 100, modules, amounts) ); @@ -1297,13 +1907,8 @@ contract PufferProtocolTest is TestHelper { guardianSignatures: signatures }); - // Total withdrawal eth is 32 + 31 + 31.6 = 94.6 - // 0.14 stays in protocol because the first amount is over 32 eth (it has the rewards in it) - - // 94.6 goes to the pools - // Default split rate for withdrawal pool is 10% - assertEq(address(withdrawalPool).balance, 9.46 ether, "ending withdrawal pool balance"); - assertEq(address(pool).balance, 85.14 ether, "ending pool balance"); + // Total withdrawal eth is 32 + 31 + 31.6 = 94.6 and the vault has starting balance of 1000 ETH + assertEq(address(pufferVault).balance, 1094.6 ether, "ending pool balance"); } function _buildMerkleProof(MerkleProofData[] memory validatorExits) internal returns (bytes32 root) { @@ -1329,6 +1934,32 @@ contract PufferProtocolTest is TestHelper { root = fullWithdrawalsMerkleProof.getRoot(fullWithdrawalMerkleProofData); } + + /** + * @dev Registers validator key and pays for everything in ETH + */ + function _registerValidatorKey(bytes32 pubKeyPart, bytes32 moduleName) internal { + uint256 numberOfDays = 30; + uint256 vtPrice = pufferOracle.getValidatorTicketPrice() * numberOfDays; + + bytes memory pubKey = _getPubKey(pubKeyPart); + + ValidatorKeyData memory validatorKeyData = _getMockValidatorKeyData(pubKey, moduleName); + + uint256 idx = pufferProtocol.getPendingValidatorIndex(moduleName); + + uint256 bond = 1 ether; + + vm.expectEmit(true, true, true, true); + emit ValidatorKeyRegistered(pubKey, idx, moduleName, true); + pufferProtocol.registerValidatorKey{ value: (vtPrice + bond) }( + validatorKeyData, moduleName, 30, emptyPermit, emptyPermit + ); + } + + function _upscaleTo18Decimals(uint256 amount) internal pure returns (uint256) { + return amount * 1 ether; + } } struct MerkleProofData { diff --git a/test/unit/ValidatorTicket.t.sol b/test/unit/ValidatorTicket.t.sol new file mode 100644 index 00000000..616833c5 --- /dev/null +++ b/test/unit/ValidatorTicket.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { ECDSA } from "openzeppelin/utils/cryptography/ECDSA.sol"; +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { TestHelper } from "../helpers/TestHelper.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; + +contract ValidatorTicketTest is TestHelper { + using ECDSA for bytes32; + using SafeTransferLib for address; + using SafeTransferLib for address payable; + + address rewardsRecipient = makeAddr("rewardsRecipient"); + + function setUp() public override { + // Just call the parent setUp() + super.setUp(); + _skipDefaultFuzzAddresses(); + } + + function test_setup() public { + assertEq(validatorTicket.name(), "Puffer Validator Ticket"); + assertEq(validatorTicket.symbol(), "VT"); + assertEq(validatorTicket.getProtocolFeeRate(), 5 * 1e18, "protocol fee rate"); // 5% + assertTrue(address(validatorTicket.PUFFER_ORACLE()) != address(0), "oracle"); + assertTrue(validatorTicket.GUARDIAN_MODULE() != address(0), "guardians"); + assertTrue(validatorTicket.PUFFER_VAULT() != address(0), "vault"); + } + + function test_funds_splitting() public { + uint256 vtPrice = pufferOracle.getValidatorTicketPrice(); + + uint256 amount = vtPrice * 2000; // 20000 VTs is 20 ETH + vm.deal(address(this), amount); + + address treasury = validatorTicket.TREASURY(); + + assertEq(validatorTicket.balanceOf(address(this)), 0, "should start with 0"); + assertEq(address(validatorTicket).balance, 0, "treasury balance should start with 0"); + assertEq(address(guardianModule).balance, 0, "guardian balance should start with 0"); + + validatorTicket.purchaseValidatorTicket{ value: amount }(address(this)); + + // 0.5% from 20 ETH is 0.1 ETH + assertEq(address(guardianModule).balance, 0.1 ether, "guardians balance"); + // 5% from 20 ETH is 1 ETH + assertEq(address(validatorTicket).balance, 1 ether, "treasury should get 1 ETH for 100 VTs"); + } + + function test_overflow_protocol_fee_rate() public { + vm.startPrank(DAO); + vm.expectRevert(bytes4(hex"35278d12")); // Overflow() selector 0x35278d12 + validatorTicket.setProtocolFeeRate(20 * FixedPointMathLib.WAD); // should revert because max fee is 18.44% (uint64) + } + + function test_change_protocol_fee_rate() public { + vm.startPrank(DAO); + + uint256 newFeeRate = 15 * FixedPointMathLib.WAD; + + vm.expectEmit(true, true, true, true); + emit ValidatorTicket.ProtocolFeeChanged(5 * FixedPointMathLib.WAD, newFeeRate); + validatorTicket.setProtocolFeeRate(newFeeRate); + + assertEq(validatorTicket.getProtocolFeeRate(), newFeeRate, "updated"); + } +} diff --git a/test/unit/WithdrawalPool.t.sol b/test/unit/WithdrawalPool.t.sol deleted file mode 100644 index 22b9fc67..00000000 --- a/test/unit/WithdrawalPool.t.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -import { Test } from "forge-std/Test.sol"; -import { Permit } from "puffer/struct/Permit.sol"; -import { TestHelper } from "../helpers/TestHelper.sol"; - -contract WithdrawalPoolTest is TestHelper { - function setUp() public override { - // Just call the parent setUp() - super.setUp(); - _skipDefaultFuzzAddresses(); - } - - function testWithdrawSomebodyElsesPufETHReverts() public { - address alice = makeAddr("alice"); - address bob = makeAddr("bob"); - address random = makeAddr("random"); - - vm.deal(address(withdrawalPool), 100 ether); - vm.deal(bob, 10 ether); - vm.deal(alice, 10 ether); - - vm.startPrank(bob); - pool.depositETH{ value: 10 ether }(); - vm.startPrank(alice); - pool.depositETH{ value: 10 ether }(); - - // Alice approves pufETH the traditional way - pool.approve(address(withdrawalPool), type(uint256).max); - - // Normal withdrawal should fail - vm.startPrank(random); - vm.expectRevert(); - withdrawalPool.withdrawETH(random, 10 ether); - - Permit memory fakePermit; - fakePermit.owner = alice; - fakePermit.amount = 10 ether; - - // Permit withdrawal should fail - vm.expectRevert(); - withdrawalPool.withdrawETH(random, fakePermit); - } - - // Test withdraw ETH if there is neough liquidity - function testWithdrawETH() public { - address bob = makeAddr("bob"); - - vm.deal(address(withdrawalPool), 100 ether); - vm.deal(bob, 10 ether); - - address charlie = makeAddr("charlie"); - - assertTrue(charlie.balance == 0, "charlie should be poor"); - - vm.startPrank(bob); - pool.depositETH{ value: 10 ether }(); - - pool.approve(address(withdrawalPool), type(uint256).max); - withdrawalPool.withdrawETH(charlie, 1 ether); - - assertTrue(charlie.balance != 0, "charlie got ETH"); - } - - // Depositor deposits and gives his signature so the withdrawer can take that signature and submit it to get the ETH - function testWithdrawETHWithSignature() public { - vm.deal(address(withdrawalPool), 1000 ether); - - string memory addressSeed = "pufETHDepositor"; - address pufETHDepositor = makeAddr(addressSeed); - - _TestTemps memory temp = _testTemps(addressSeed, address(withdrawalPool), 50 ether, block.timestamp); - - Permit memory permit = _signPermit(temp); - - vm.deal(pufETHDepositor, 1000 ether); - - address charlie = makeAddr("charlie"); - - assertTrue(charlie.balance == 0, "charlie should be poor"); - - vm.startPrank(pufETHDepositor); - pool.depositETH{ value: 1000 ether }(); - - withdrawalPool.withdrawETH(charlie, permit); - - assertTrue(charlie.balance != 0, "charlie got ETH"); - } -}