diff --git a/.gitmodules b/.gitmodules index ab95d5962..75cd8bf20 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,7 +5,7 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable - commit = 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 + commit = 22489db15621b9a42ebddb1facade6962034e9b9 [submodule "lib/safe-smart-account"] path = lib/safe-smart-account url = https://github.com/safe-global/safe-smart-account @@ -17,4 +17,4 @@ [submodule "lib/eigenlayer-contracts"] path = lib/eigenlayer-contracts url = https://github.com/layr-labs/eigenlayer-contracts - commit = 90a0f6aee79b4a38e1b63b32f9627f21b1162fbb + commit = bda003385c5fec59e35196dc14d01f17d1eb7001 \ No newline at end of file diff --git a/README.md b/README.md index a3b48d4fa..41e30b23d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # YieldNest Protocol -## Mainnet v0.0.3 +## Mainnet -### Proxies +### Contracts | Contract Name | Contract Address | |----------------------------|-------------------------------------------| | ynETH Proxy | [`0x09db87A538BD693E9d08544577d5cCfAA6373A48`](https://etherscan.io/address/0x09db87A538BD693E9d08544577d5cCfAA6373A48) | @@ -10,31 +10,18 @@ | RewardsDistributor Proxy | [`0x40d5FF3E218f54f4982661a0464a298Cf6652351`](https://etherscan.io/address/0x40d5FF3E218f54f4982661a0464a298Cf6652351) | | ExecutionLayerReceiver Proxy | [`0x1D6b2a11FFEa5F9a8Ed85A02581910b3d695C12b`](https://etherscan.io/address/0x1D6b2a11FFEa5F9a8Ed85A02581910b3d695C12b) | | ConsensusLayerReceiver Proxy | [`0xE439fe4563F7666FCd7405BEC24aE7B0d226536e`](https://etherscan.io/address/0xE439fe4563F7666FCd7405BEC24aE7B0d226536e) | +| ynViewer | [`0x5c7C7246bD8a18DF8e0F498b1E3EB1abF5D3118E`](https://etherscan.io/address/0xF0207Ffa0b793E009DF9Df62fEE95B8FC6c93EcF) | +| ynETH Withdrawal Queue Manager | [`0x5c7C7246bD8a18DF8e0F498b1E3EB1abF5D3118E`](https://etherscan.io/address/0x0BC9BC81aD379810B36AD5cC95387112990AA67b) | +| ynETH Redemption Assets Vault | [`0xF0207Ffa0b793E009DF9Df62fEE95B8FC6c93EcF`](https://etherscan.io/address/0x5D6e53c42E3B37f82F693937BC508940769c5caf) | +| WithdrawalsProcessor | [`0x61810a90128ee5c5f5a3730f0449da9e9480f888`](https://etherscan.io/address/0x6d052CdEd3F64aea51f6051F33b68b42016C5FbA) | -### Implementations -| Contract Name | Contract Address | -|----------------------------|-------------------------------------------| -| ynETH | [`0x14DC3d915107DcA9ed39E29E14fbdfe4358a1346`](https://etherscan.io/address/0x14DC3d915107DcA9ed39E29E14fbdfe4358a1346) | -| StakingNodesManager | [`0xaBD3A755E4eEF24f862c268D21Fd5235904cc811`](https://etherscan.io/address/0xaBD3A755E4eEF24f862c268D21Fd5235904cc811) | -| RewardsDistributor | [`0x306EBed66642867B94b29752be37b8f20fEb5F72`](https://etherscan.io/address/0x306EBed66642867B94b29752be37b8f20fEb5F72) | -| ExecutionLayerReceiver | [`0x863Ba8f64b1528De6c8A7239D80D4f2b7CfF159B`](https://etherscan.io/address/0x863Ba8f64b1528De6c8A7239D80D4f2b7CfF159B) | -| ConsensusLayerReceiver | [`0xa98e8652DAF2407839b18f4e215D86D88Eb91582`](https://etherscan.io/address/0xa98e8652DAF2407839b18f4e215D86D88Eb91582) | -| StakingNode | [`0xF8A37F34a427611C4c54F82bF43230FA041d237B`](https://etherscan.io/address/0xF8A37F34a427611C4c54F82bF43230FA041d237B#code) | -### Proxy Admins -| Contract Name | Proxy Admin Address | -|----------------------------|-------------------------------------------| -| ynETH ProxyAdmin | [`0x12e9DA7e671342E7A5fDD7197348C8019e72C31F`](https://etherscan.io/address/0x12e9DA7e671342E7A5fDD7197348C8019e72C31F) | -| StakingNodesManager ProxyAdmin | [`0x091929600D3891861615098fc9eA3a0270966398`](https://etherscan.io/address/0x091929600D3891861615098fc9eA3a0270966398) | -| RewardsDistributor ProxyAdmin | [`0x17ecBFce53B364Fd571D159eA2b766bA3114d85E`](https://etherscan.io/address/0x17ecBFce53B364Fd571D159eA2b766bA3114d85E) | -| ExecutionLayerReceiver ProxyAdmin | [`0x5a3d6625C6d26E3650675bF2b7041cd7d16A63B2`](https://etherscan.io/address/0x5a3d6625C6d26E3650675bF2b7041cd7d16A63B2) | -| ConsensusLayerReceiver ProxyAdmin | [`0xaB5CC95BD4F5C45F637B6D319169Bbbc212CfEE8`](https://etherscan.io/address/0xaB5CC95BD4F5C45F637B6D319169Bbbc212CfEE8) | -## Holesky v0.0.1 +## Holesky -### Proxies +### Contracts | Contract Name | Contract Address | |----------------------------|-------------------------------------------| @@ -44,25 +31,9 @@ | YieldNestOracle Proxy | [`0xe13ea7327507a9f8e3d3ef74635ecb1e375ed98c`](https://holesky.etherscan.io/address/0xe13ea7327507a9f8e3d3ef74635ecb1e375ed98c) | | ExecutionLayerReceiver Proxy | [`0xa5e9e1ceb4cc1854d0e186a9b3e67158b84ad072`](https://holesky.etherscan.io/address/0xa5e9e1ceb4cc1854d0e186a9b3e67158b84ad072) | | ConsensusLayerReceiver Proxy | [`0x706eed02702ffe9cbefd6a65e63f3c2b59b7ef2d`](https://holesky.etherscan.io/address/0x706eed02702ffe9cbefd6a65e63f3c2b59b7ef2d) | +| ynViewer | [`0x5c7C7246bD8a18DF8e0F498b1E3EB1abF5D3118E`](https://holesky.etherscan.io/address/0x61810a90128ee5c5f5a3730f0449da9e9480f888) | +| ynETH Withdrawal Queue Manager | [`0x0BC9BC81aD379810B36AD5cC95387112990AA67b`](https://holesky.etherscan.io/address/0x141aAb320857145fB42240C979b800f48CE5B678) | +| ynETH Redemption Assets Vault | [`0x5D6e53c42E3B37f82F693937BC508940769c5caf`](https://holesky.etherscan.io/address/0x3a2DD2f0f5A20768110a52fC4f091AB9d8631b58) | +| WithdrawalsProcessor | [`0x6d052CdEd3F64aea51f6051F33b68b42016C5FbA`](https://holesky.etherscan.io/address/0x48E3FdCE3E2d5A3Fa34bdEd9eb9dEeBB48217ba3) | -### Implementations - -| Contract Name | Contract Address | -|----------------------------|-------------------------------------------| -| ynETH | [`0x0E60A5db38c545ced3d3480B6768811cc603649A`](https://holesky.etherscan.io/address/0x0E60A5db38c545ced3d3480B6768811cc603649A) | -| StakingNodesManager | [`0xDDd8f9Ad981444a0C44c611E9438b06FFc2bd354`](https://holesky.etherscan.io/address/0xDDd8f9Ad981444a0C44c611E9438b06FFc2bd354) | -| RewardsReceiver | [`0x1fbedf3773418f20b9dfeafcd9d263030eb0e42f`](https://holesky.etherscan.io/address/0x1fbedf3773418f20b9dfeafcd9d263030eb0e42f) | -| RewardsReceiver | [`0xe7acc0533c650ad0cc11f57f81c38fa19634b1d7`](https://holesky.etherscan.io/address/0xe7acc0533c650ad0cc11f57f81c38fa19634b1d7) | -| StakingNode | [`0xe01a7328090c95e89Ec689b005392Cf7e5De90d9`](https://holesky.etherscan.io/address/0xe01a7328090c95e89Ec689b005392Cf7e5De90d9) | -| RewardsDistributor | [`0xb6ec4d9f71e437c672147c576f1c70ba5da8d159`](https://holesky.etherscan.io/address/0xb6ec4d9f71e437c672147c576f1c70ba5da8d159) | -### Proxy Admins - -| Contract Name | Proxy Admin Address | -|----------------------------|-------------------------------------------| -| ynETH ProxyAdmin | [`0x4ed4d42a2001d240e20304f58c626a63a3eb7ff4`](https://holesky.etherscan.io/address/0x4ed4d42a2001d240e20304f58c626a63a3eb7ff4) | -| StakingNodesManager ProxyAdmin | [`0xb0165e7584ebf6d76f0dd95e58dea6bdd759f238`](https://holesky.etherscan.io/address/0xb0165e7584ebf6d76f0dd95e58dea6bdd759f238) | -| RewardsDistributor ProxyAdmin | [`0xc0300eefe38a393681b6eca7912985a165d37c0e`](https://holesky.etherscan.io/address/0xc0300eefe38a393681b6eca7912985a165d37c0e) | -| YieldNestOracle ProxyAdmin | [`0x16b0b532c7512035a812c59b04e65f135ad12142`](https://holesky.etherscan.io/address/0x16b0b532c7512035a812c59b04e65f135ad12142) | -| ExecutionLayerReceiver ProxyAdmin | [`0x319f70591f72c5d69823dfc673acdfefd6aee5a1`](https://holesky.etherscan.io/address/0x319f70591f72c5d69823dfc673acdfefd6aee5a1) | -| ConsensusLayerReceiver ProxyAdmin | [`0x6234fd395c1f090334606efbf3cbf3be46fcf177`](https://holesky.etherscan.io/address/0x6234fd395c1f090334606efbf3cbf3be46fcf177) | diff --git a/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-1728146182.json b/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-1728146182.json new file mode 100644 index 000000000..30988bc16 --- /dev/null +++ b/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-1728146182.json @@ -0,0 +1,228 @@ +{ + "transactions": [ + { + "hash": "0x98efd40a4f378894b0f6222a3cf5653b05cee5fa5b44a5c70f8fed3be58e41d1", + "transactionType": "CREATE", + "contractName": "WithdrawalsProcessor", + "contractAddress": "0x36a38aa91947dbe6539e19512e6ff26576015bb2", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x1205e0", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b5061001861001d565b6100cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561006d5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100cc5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610ef9806100dc5f395ff3fe608060405234801561000f575f80fd5b50600436106100a6575f3560e01c806391d148541161006e57806391d1485414610145578063a217fddf14610158578063ad7805e81461015f578063c0c53b8b14610186578063c949761814610199578063d547741f146101ac575f80fd5b806301ffc9a7146100aa578063248a9ca3146100d25780632f2ff15d146100f357806336568abe14610108578063443dc4281461011b575b5f80fd5b6100bd6100b836600461086e565b6101bf565b60405190151581526020015b60405180910390f35b6100e56100e036600461089c565b6101f5565b6040519081526020016100c9565b6101066101013660046108d7565b610215565b005b6101066101163660046108d7565b610237565b5f5461012d906001600160a01b031681565b6040516001600160a01b0390911681526020016100c9565b6100bd6101533660046108d7565b61026f565b6100e55f81565b6100e57fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9881565b610106610194366004610905565b6102a5565b6101066101a7366004610c0e565b610456565b6101066101ba3660046108d7565b610697565b5f6001600160e01b03198216637965db0b60e01b14806101ef57506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f9081525f80516020610ea4833981519152602052604090206001015490565b61021e826101f5565b610227816106b3565b61023183836106c0565b50505050565b6001600160a01b03811633146102605760405163334bd91960e11b815260040160405180910390fd5b61026a8282610761565b505050565b5f9182525f80516020610ea4833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156102ea5750825b90505f8267ffffffffffffffff1660011480156103065750303b155b905081158015610314575080155b156103325760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561035c57845460ff60401b1916600160401b1785555b876001600160a01b0381166103845760405163d92e233d60e01b815260040160405180910390fd5b866001600160a01b0381166103ac5760405163d92e233d60e01b815260040160405180910390fd5b6103b46107da565b5f80546001600160a01b0319166001600160a01b038c161781556103d8908a6106c0565b506104037fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe98896106c0565b505050831561044c57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe98610480816106b3565b5f5484516040516238a78560e71b81526001600160a01b0390921691631c53c280916104b29160040190815260200190565b602060405180830381865afa1580156104cd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104f19190610caf565b6001600160a01b031663f55b304084846040518363ffffffff1660e01b815260040161051e929190610d3c565b5f604051808303815f87803b158015610535575f80fd5b505af1158015610547573d5f803e3d5ffd5b505f9250600191506105569050565b6040519080825280602002602001820160405280156105ae57816020015b61059b60405180608001604052805f81526020015f81526020015f81526020015f81525090565b8152602001906001900390816105745790505b50905084815f815181106105c4576105c4610e21565b60209081029190910101525f5460405163c108f0cb60e01b81526001600160a01b039091169063c108f0cb906105fe908490600401610e35565b5f604051808303815f87803b158015610615575f80fd5b505af1158015610627573d5f803e3d5ffd5b505050507f39e5d24452915384663624d58b879d5ac4c8f63584b740ec27548706d34ea9bb85855160405161068892919082518152602080840151908201526040808401519082015260609283015192810192909252608082015260a00190565b60405180910390a15050505050565b6106a0826101f5565b6106a9816106b3565b6102318383610761565b6106bd81336107e4565b50565b5f5f80516020610ea48339815191526106d9848461026f565b610758575f848152602082815260408083206001600160a01b03871684529091529020805460ff1916600117905561070e3390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019150506101ef565b5f9150506101ef565b5f5f80516020610ea483398151915261077a848461026f565b15610758575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a460019150506101ef565b6107e2610825565b565b6107ee828261026f565b6108215760405163e2517d3f60e01b81526001600160a01b03821660048201526024810183905260440160405180910390fd5b5050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166107e257604051631afcd79f60e31b815260040160405180910390fd5b5f6020828403121561087e575f80fd5b81356001600160e01b031981168114610895575f80fd5b9392505050565b5f602082840312156108ac575f80fd5b5035919050565b6001600160a01b03811681146106bd575f80fd5b80356108d2816108b3565b919050565b5f80604083850312156108e8575f80fd5b8235915060208301356108fa816108b3565b809150509250929050565b5f805f60608486031215610917575f80fd5b8335610922816108b3565b92506020840135610932816108b3565b91506040840135610942816108b3565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160e0810167ffffffffffffffff811182821017156109845761098461094d565b60405290565b6040516080810167ffffffffffffffff811182821017156109845761098461094d565b604051601f8201601f1916810167ffffffffffffffff811182821017156109d6576109d661094d565b604052919050565b5f67ffffffffffffffff8211156109f7576109f761094d565b5060051b60200190565b803563ffffffff811681146108d2575f80fd5b5f82601f830112610a23575f80fd5b81356020610a38610a33836109de565b6109ad565b8083825260208201915060208460051b870101935086841115610a59575f80fd5b602086015b84811015610a7e578035610a71816108b3565b8352918301918301610a5e565b509695505050505050565b5f82601f830112610a98575f80fd5b81356020610aa8610a33836109de565b8083825260208201915060208460051b870101935086841115610ac9575f80fd5b602086015b84811015610a7e5780358352918301918301610ace565b5f82601f830112610af4575f80fd5b81356020610b04610a33836109de565b82815260059290921b84018101918181019086841115610b22575f80fd5b8286015b84811015610a7e57803567ffffffffffffffff80821115610b45575f80fd5b9088019060e0828b03601f1901811315610b5d575f80fd5b610b65610961565b610b708885016108c7565b81526040610b7f8186016108c7565b898301526060610b908187016108c7565b828401526080915081860135818401525060a0610bae818701610a01565b8284015260c091508186013585811115610bc6575f80fd5b610bd48f8c838a0101610a14565b82850152505082850135925083831115610bec575f80fd5b610bfa8d8a85880101610a89565b908201528652505050918301918301610b26565b5f805f83850360c0811215610c21575f80fd5b6080811215610c2e575f80fd5b50610c3761098a565b8435815260208501356020820152604085013560408201526060850135606082015280935050608084013567ffffffffffffffff80821115610c77575f80fd5b610c8387838801610ae5565b935060a0860135915080821115610c98575f80fd5b50610ca586828701610a89565b9150509250925092565b5f60208284031215610cbf575f80fd5b8151610895816108b3565b5f815180845260208085019450602084015f5b83811015610d025781516001600160a01b031687529582019590820190600101610cdd565b509495945050505050565b5f815180845260208085019450602084015f5b83811015610d0257815187529582019590820190600101610d20565b5f6040808301604084528086518083526060925060608601915060608160051b8701016020808a015f5b84811015610e0157898403605f19018652815180516001600160a01b03908116865284820151811685870152898201511689860152878101518886015260808082015163ffffffff169086015260a08082015160e08288018190529190610dcf83890182610cca565b9250505060c08083015192508682038188015250610ded8183610d0d565b978501979550505090820190600101610d66565b505087820390880152610e148189610d0d565b9998505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b602080825282518282018190525f9190848201906040850190845b81811015610e9757610e84838551805182526020810151602083015260408101516040830152606081015160608301525050565b9284019260809290920191600101610e50565b5090969550505050505056fe02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a264697066735822122099c24f642bdfb75892d6153155fe0f1227c1c9245ddfee88a0898ec3497cd19f64736f6c63430008180033", + "nonce": "0x1cc", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e8f9de288bfbd536dd09a2d3ea6271170b6a8f6914704f0da732f896652edba", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "function": null, + "arguments": [ + "0x36a38AA91947DbE6539e19512E6FF26576015Bb2", + "0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913", + "0x" + ], + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610300000000000000000000000036a38aa91947dbe6539e19512e6ff26576015bb2000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b91300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x1cd", + "chainId": "0x4268" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0x22d9bc7555875af63a51c20026f02e752781a8c0", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + } + ], + "isFixedGasLimit": false + }, + { + "hash": "0xf6c816e5ae6c0270e89f0fe0bcb2573070f82075813ddb11b908669fabed7ed2", + "transactionType": "CALL", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "gas": "0x2c108", + "value": "0x0", + "input": "0xc0c53b8b000000000000000000000000c2387ebb4ea66627e3543a771e260bd84218d6a1000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b9130000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d39", + "nonce": "0x1ce", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x430149", + "logs": [ + { + "address": "0x36a38aa91947dbe6539e19512e6ff26576015bb2", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0x98efd40a4f378894b0f6222a3cf5653b05cee5fa5b44a5c70f8fed3be58e41d1", + "transactionIndex": "0x50", + "logIndex": "0x27", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000008000000000000000000000000000000000000000000000000000800000000000000000000100080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x98efd40a4f378894b0f6222a3cf5653b05cee5fa5b44a5c70f8fed3be58e41d1", + "transactionIndex": "0x50", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "gasUsed": "0xddd23", + "effectiveGasPrice": "0x216d12c", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x36a38aa91947dbe6539e19512e6ff26576015bb2" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x4cee2d", + "logs": [ + { + "address": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000036a38aa91947dbe6539e19512e6ff26576015bb2" + ], + "data": "0x", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0x6e8f9de288bfbd536dd09a2d3ea6271170b6a8f6914704f0da732f896652edba", + "transactionIndex": "0x51", + "logIndex": "0x28", + "removed": false + }, + { + "address": "0x22d9bc7555875af63a51c20026f02e752781a8c0", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + ], + "data": "0x", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0x6e8f9de288bfbd536dd09a2d3ea6271170b6a8f6914704f0da732f896652edba", + "transactionIndex": "0x51", + "logIndex": "0x29", + "removed": false + }, + { + "address": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022d9bc7555875af63a51c20026f02e752781a8c0", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0x6e8f9de288bfbd536dd09a2d3ea6271170b6a8f6914704f0da732f896652edba", + "transactionIndex": "0x51", + "logIndex": "0x2a", + "removed": false + } + ], + "logsBloom": "0x00040000000000000000000000000000400000000010000000800000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000418000000002000081000000000000000000000000000000000000020000000000000000000800000000804000000010000000000000400000000000000080000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000400800000000000000020000000000000000400000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e8f9de288bfbd536dd09a2d3ea6271170b6a8f6914704f0da732f896652edba", + "transactionIndex": "0x51", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x216d12c", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x4ed040", + "logs": [ + { + "address": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0xf6c816e5ae6c0270e89f0fe0bcb2573070f82075813ddb11b908669fabed7ed2", + "transactionIndex": "0x52", + "logIndex": "0x2b", + "removed": false + }, + { + "address": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0xe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe98", + "0x0000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d39", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0xf6c816e5ae6c0270e89f0fe0bcb2573070f82075813ddb11b908669fabed7ed2", + "transactionIndex": "0x52", + "logIndex": "0x2c", + "removed": false + }, + { + "address": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "transactionHash": "0xf6c816e5ae6c0270e89f0fe0bcb2573070f82075813ddb11b908669fabed7ed2", + "transactionIndex": "0x52", + "logIndex": "0x2d", + "removed": false + } + ], + "logsBloom": "0x00040084000000000000000000000400000000000800000000000000000000000000000000000004000010000000000000000000000000000000000000000000000000000000000000008000000000000080000000000000000000000000000000000000020000002000000000000900000000000000000000000000000000000000000000000080000800000000000000000002000080000000000000000000000800000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000004000100000000000020000000000000000400000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xf6c816e5ae6c0270e89f0fe0bcb2573070f82075813ddb11b908669fabed7ed2", + "transactionIndex": "0x52", + "blockHash": "0x468e16d7442b3d0123ef5aa3827a5ed279c865cc0996e65e64407b745cc3fd97", + "blockNumber": "0x25bdf5", + "gasUsed": "0x1e213", + "effectiveGasPrice": "0x216d12c", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x48e3fdce3e2d5a3fa34bded9eb9deebb48217ba3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728146182, + "chain": 17000, + "commit": "a61cfaa" +} \ No newline at end of file diff --git a/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-latest.json b/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-latest.json new file mode 100644 index 000000000..9c3d49472 --- /dev/null +++ b/broadcast/DeployYnETHWithdrawals.s.sol/17000/run-latest.json @@ -0,0 +1,622 @@ +{ + "transactions": [ + { + "hash": "0x01ed70f32a7f0fd06a98a9780c661b2be964324c6707d033786764a72df8876e", + "transactionType": "CREATE", + "contractName": "StakingNodesManager", + "contractAddress": "0xc9cf6740282617f3b392f900de0449e687ce05e3", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x50e4e6", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61486c80620000e35f395ff3fe608060405260043610620002fe575f3560e01c806391d14854116200018e578063cb70bd7911620000ea578063def8c189116200009e578063f33de6c01162000076578063f33de6c01462000a23578063f3513a371462000a39578063fb1bb9de1462000a5f578063ff50abdc1462000a94575f80fd5b8063def8c18914620009a9578063e63ab1e914620009cd578063ea4d3c9b1462000a02575f80fd5b8063cb70bd7914620008ee578063cd3cddb31462000905578063d02e02cf1462000929578063d2609520146200094d578063d27e80e8146200096e578063d547741f1462000985575f80fd5b8063ad7805e81162000142578063ad7805e8146200080a578063b1b623d8146200083f578063ba4eb2c41462000874578063ba8b9761146200088f578063c108f0cb14620008a6578063c9fd18b314620008ca575f80fd5b806391d14854146200073e5780639872ab8414620007625780639ab739d314620007835780639e2eaadb14620007b8578063a1174e7d14620007cf578063a217fddf14620007f5575f80fd5b806336568abe116200025a5780635fc90c56116200020e5780637d1f8a8111620001e65780637d1f8a8114620006bc57806382699d5a14620006e257806384f9b77a1462000703578063863928511462000727575f80fd5b80635fc90c56146200063f57806361464d2d146200067457806361669d271462000698575f80fd5b806336568abe146200057157806339b70e3814620005955780633f2a554014620005b65780634665bcda14620005d7578063555344df14620005f75780635f8ad023146200061b575f80fd5b8063239d85ab11620002b2578063239d85ab1462000478578063248a9ca3146200049e5780632b5cfa8114620004c25780632b828f5c14620004f55780632f2ff15d146200051957806335aa2e44146200053d575f80fd5b806301ffc9a7146200034257806304f67aa1146200037b5780630dc3e02914620003ad57806315d36e7214620003d15780631c53c28014620004065780632118a4bb1462000443575f80fd5b366200033e57604080513381523460208201527fbfe611b001dfcd411432f7bf0d79b82b4b2ee81511edac123a3403c357fb972a910160405180910390a1005b5f80fd5b3480156200034e575f80fd5b50620003666200036036600462003435565b62000aab565b60405190151581526020015b60405180910390f35b34801562000387575f80fd5b506200039e5f80516020620047f783398151915281565b60405190815260200162000372565b348015620003b9575f80fd5b5062000366620003cb36600462003473565b62000ae2565b348015620003dd575f80fd5b506200039e7f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf75881565b34801562000412575f80fd5b506200042a6200042436600462003491565b62000b0f565b6040516001600160a01b03909116815260200162000372565b3480156200044f575f80fd5b506200039e7ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c0638581565b34801562000484575f80fd5b506200049c6200049636600462003473565b62000b38565b005b348015620004aa575f80fd5b506200039e620004bc36600462003491565b62000c98565b348015620004ce575f80fd5b50620004e6620004e036600462003473565b62000cb9565b604051620003729190620034fa565b34801562000501575f80fd5b506200049c620005133660046200350e565b62000cfe565b34801562000525575f80fd5b506200049c6200053736600462003525565b62000f4b565b34801562000549575f80fd5b50620005616200055b36600462003491565b62000f73565b6040516200037292919062003556565b3480156200057d575f80fd5b506200049c6200058f36600462003525565b62001031565b348015620005a1575f80fd5b506004546200042a906001600160a01b031681565b348015620005c2575f80fd5b506007546200042a906001600160a01b031681565b348015620005e3575f80fd5b505f546200042a906001600160a01b031681565b34801562000603575f80fd5b506200049c6200061536600462003579565b6200106c565b34801562000627575f80fd5b506200049c6200063936600462003579565b620010dd565b3480156200064b575f80fd5b506200039e7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff508881565b34801562000680575f80fd5b506200049c6200069236600462003491565b620012fe565b348015620006a4575f80fd5b50620004e6620006b636600462003491565b62001354565b348015620006c8575f80fd5b50600c546200042a9061010090046001600160a01b031681565b348015620006ee575f80fd5b506001546200042a906001600160a01b031681565b3480156200070f575f80fd5b506200049c62000721366004620035ec565b620013f3565b6200049c62000738366004620035fe565b62001611565b3480156200074a575f80fd5b50620003666200075c36600462003525565b6200167f565b3480156200076e575f80fd5b506006546200042a906001600160a01b031681565b3480156200078f575f80fd5b506200039e7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b81565b348015620007c4575f80fd5b506200042a620016b6565b348015620007db575f80fd5b50620007e66200188c565b60405162000372919062003626565b34801562000801575f80fd5b506200039e5f81565b34801562000816575f80fd5b506200039e7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9881565b3480156200084b575f80fd5b506200039e7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d581565b34801562000880575f80fd5b50600c54620003669060ff1681565b3480156200089b575f80fd5b506200049c620018ee565b348015620008b2575f80fd5b506200049c620008c4366004620036e6565b62001961565b348015620008d6575f80fd5b506200039e620008e836600462003809565b620019c9565b348015620008fa575f80fd5b506200039e60095481565b34801562000911575f80fd5b506200049c6200092336600462003473565b620019e5565b34801562000935575f80fd5b50620003666200094736600462003473565b62001af3565b34801562000959575f80fd5b506005546200042a906001600160a01b031681565b3480156200097a575f80fd5b506200049c62001b20565b34801562000991575f80fd5b506200049c620009a336600462003525565b62001b87565b348015620009b5575f80fd5b5062000366620009c736600462003473565b62001ba9565b348015620009d9575f80fd5b506200039e7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b34801562000a0e575f80fd5b506002546200042a906001600160a01b031681565b34801562000a2f575f80fd5b506008546200039e565b34801562000a45575f80fd5b5062000a5062001bd6565b604051620003729190620038fb565b34801562000a6b575f80fd5b506200039e7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b34801562000aa0575f80fd5b506200039e62001cd5565b5f6001600160e01b03198216637965db0b60e01b148062000adc57506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f62000adc7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b836200167f565b6008818154811062000b1f575f80fd5b5f918252602090912001546001600160a01b0316905081565b5f80516020620047f783398151915262000b528162001e21565b816001600160a01b03811662000b7b5760405163d92e233d60e01b815260040160405180910390fd5b6005546001600160a01b031662000ba557604051636aef646b60e11b815260040160405180910390fd5b600554604051631b2ce7f360e11b81526001600160a01b03858116600483015290911690633659cfe6906024015f604051808303815f87803b15801562000bea575f80fd5b505af115801562000bfd573d5f803e3d5ffd5b505060085491505f90505b8181101562000c4f5762000c466008828154811062000c2b5762000c2b62003975565b5f918252602090912001546001600160a01b03168362001e30565b60010162000c08565b50604080516001600160a01b0386168152602081018390527f02c79c1e3f8b682a9487f84feb1ab388b1ebf7a5e85307e941ec19122d2ed14e910160405180910390a150505050565b5f9081525f8051602062004817833981519152602052604090206001015490565b60408051600160f81b60208201525f6021820152606083811b6bffffffffffffffffffffffff1916602c83015291016040516020818303038152906040529050919050565b62000d0d602082018262003473565b6001600160a01b03811662000d355760405163d92e233d60e01b815260040160405180910390fd5b62000d47604083016020840162003473565b6001600160a01b03811662000d6f5760405163d92e233d60e01b815260040160405180910390fd5b62000d81606084016040850162003473565b6001600160a01b03811662000da95760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805460029190600160401b900460ff168062000df3575080546001600160401b03808416911610155b1562000e125760405163f92ee8a960e01b815260040160405180910390fd5b805468ffffffffffffffffff19166001600160401b03831617600160401b1781555f62000e3f8162001e21565b62000e4e602088018862003473565b600c80546001600160a01b039290921661010002610100600160a81b031990921691909117905562000eb27fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9862000eac60408a0160208b0162003473565b62002039565b5062000eea7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b62000eac60608a0160408b0162003473565b5050600380546001600160a01b0319169055805460ff60401b191681556040516001600160401b03831681527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050505050565b62000f568262000c98565b62000f618162001e21565b62000f6d838362002039565b50505050565b600a818154811062000f83575f80fd5b905f5260205f2090600202015f91509050805f01805462000fa49062003989565b80601f016020809104026020016040519081016040528092919081815260200182805462000fd29062003989565b8015620010215780601f1062000ff75761010080835404028352916020019162001021565b820191905f5260205f20905b8154815290600101906020018083116200100357829003601f168201915b5050505050908060010154905082565b6001600160a01b03811633146200105b5760405163334bd91960e11b815260040160405180910390fd5b620010678282620020e1565b505050565b6008545f5b8281101562000f6d575f84848381811062001090576200109062003975565b9050602002810190620010a49190620039bd565b606001359050828110620010d357604051630fd3716f60e01b8152600481018290526024015b60405180910390fd5b5060010162001071565b7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d5620011098162001e21565b620011136200215f565b600c5460ff16156200113857604051634ecaf21960e11b815260040160405180910390fd5b5f8290036200115a5760405163f0a35a8760e01b815260040160405180910390fd5b6200116683836200106c565b5f6200117c6801bc16d674ec80000084620039f0565b6006546040516378a1085360e11b8152600481018390529192506001600160a01b03169063f14210a6906024015f604051808303815f87803b158015620011c1575f80fd5b505af1158015620011d4573d5f803e3d5ffd5b508592505f9150505b81811015620012d15736868683818110620011fc57620011fc62003975565b9050602002810190620012109190620039bd565b9050600b62001220828062003a0a565b6040516200123092919062003a4f565b9081526040519081900360200190205460ff1615620012725762001255818062003a0a565b60405163055ce5e960e01b8152600401620010ca92919062003a86565b6001600b62001282838062003a0a565b6040516200129292919062003a4f565b908152604051908190036020019020805491151560ff19909216919091179055620012c7816801bc16d674ec800000620021aa565b50600101620011dd565b5050506200106760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b5f80516020620047f7833981519152620013188162001e21565b60098290556040518281527f5179ce38a6079ec731d2e265edcf9e1635e8ac7fd689ad42c5e20a7571c054159060200160405180910390a15050565b60605f600883815481106200136d576200136d62003975565b5f9182526020918290200154604080516351d5709b60e11b815290516001600160a01b039092169263a3aae136926004808401938290030181865afa158015620013b9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620013df919062003aa3565b9050620013ec8162000cb9565b9392505050565b620014076101408201610120830162003473565b6001600160a01b0381166200142f5760405163d92e233d60e01b815260040160405180910390fd5b620014436101608301610140840162003473565b6001600160a01b0381166200146b5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f81158015620014b05750825b90505f826001600160401b03166001148015620014cc5750303b155b905081158015620014db575080155b15620014fa5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156200152557845460ff60401b1916600160401b1785555b6200152f6200242f565b620015396200243b565b62001544886200244f565b6200154f8862002779565b6200156361016089016101408a0162003473565b600780546001600160a01b0319166001600160a01b0392909216919091179055610100880135600955620015a061014089016101208a0162003473565b600680546001600160a01b0319166001600160a01b039290921691909117905583156200160757845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b336001600160a01b03166008838154811062001631576200163162003975565b5f918252602090912001546001600160a01b0316146200166e57604051635975744160e01b815233600482015260248101839052604401620010ca565b6200167b8282346200293f565b5050565b5f9182525f8051602062004817833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b6005545f906001600160a01b031680620016e35760405163d92e233d60e01b815260040160405180910390fd5b7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff50886200170f8162001e21565b60085460095481106200173c57600954604051634ec828df60e11b8152600401620010ca91815260200190565b6005546040515f916001600160a01b031690620017599062003419565b6001600160a01b0390911681526040602082018190525f90820152606001604051809103905ff08015801562001791573d5f803e3d5ffd5b50905080620017a1818462001e30565b5f816001600160a01b0316630b10b2016040518163ffffffff1660e01b81526004016020604051808303815f875af1158015620017e0573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001806919062003aa3565b600880546001810182555f9182527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30180546001600160a01b0319166001600160a01b038681169182179092556040519394509084169290917fd343f0ba00451215cbdbdbcd008daa9a218952bce2ba4e283e72d722f0a2ce0791a35094505050505090565b60606008805480602002602001604051908101604052809291908181526020018280548015620018e457602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311620018c5575b5050505050905090565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6200191a8162001e21565b600c805460ff191660019081179091556040519081527f73da136f9aaeb50b1d3304d0410c5b6bf28e6003ad23ab5d3a8b0df1c1c8d583906020015b60405180910390a150565b7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe986200198d8162001e21565b5f5b82518110156200106757620019c0838281518110620019b257620019b262003975565b602002602001015162002b0e565b6001016200198f565b5f620019da87878787878762002e47565b979650505050505050565b5f80516020620047f7833981519152620019ff8162001e21565b816001600160a01b03811662001a285760405163d92e233d60e01b815260040160405180910390fd5b6005546001600160a01b03161562001a535760405163a81fbfef60e01b815260040160405180910390fd5b823060405162001a639062003427565b6001600160a01b03928316815291166020820152604001604051809103905ff08015801562001a94573d5f803e3d5ffd5b50600580546001600160a01b0319166001600160a01b039283169081179091556040805191825291851660208201527fd13f13f7d12bbc1ec3c2f99ed663d6b643f722a4654c11d4325b35ed88d9fb75910160405180910390a1505050565b5f62000adc7ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c06385836200167f565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a62001b4c8162001e21565b600c805460ff191690556040515f81527f73da136f9aaeb50b1d3304d0410c5b6bf28e6003ad23ab5d3a8b0df1c1c8d5839060200162001956565b62001b928262000c98565b62001b9d8162001e21565b62000f6d8383620020e1565b5f62000adc7f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf758836200167f565b6060600a805480602002602001604051908101604052809291908181526020015f905b8282101562001ccc578382905f5260205f2090600202016040518060400160405290815f8201805462001c2c9062003989565b80601f016020809104026020016040519081016040528092919081815260200182805462001c5a9062003989565b801562001ca95780601f1062001c7f5761010080835404028352916020019162001ca9565b820191905f5260205f20905b81548152906001019060200180831162001c8b57829003601f168201915b505050505081526020016001820154815250508152602001906001019062001bf9565b50505050905090565b6008545f9081805b8281101562001d85576008818154811062001cfc5762001cfc62003975565b5f918252602091829020015460408051630dd28e5360e31b815290516001600160a01b0390921692636e947298926004808401938290030181865afa15801562001d48573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001d6e919062003ac1565b62001d7a908362003ad9565b915060010162001cdd565b50600c5461010090046001600160a01b03161562000adc57600c60019054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001def573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e15919062003ac1565b620013ec908262003ad9565b62001e2d8133620031c6565b50565b5f826001600160a01b031663b3c650156040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001e6e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e94919062003aef565b9050806001600160401b03165f0362001fcc576040805180820182523081526020810184815291516377b3492760e01b815290516001600160a01b03908116600483015291516024820152908416906377b34927906044015f604051808303815f87803b15801562001f04575f80fd5b505af115801562001f17573d5f803e3d5ffd5b50505050826001600160a01b031663b3c650156040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001f58573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001f7e919062003aef565b604080516001600160a01b03861681526001600160401b03831660208201529192507fa070c83df7efc58b668afb64705222890e09b4090d56ee9981fc66ac1c552aa1910160405180910390a15b806001600160401b031660010362001067576040516316d384a360e21b81525f60048201526001600160a01b03841690635b4e128c906024015f604051808303815f87803b1580156200201d575f80fd5b505af115801562002030573d5f803e3d5ffd5b50505050505050565b5f5f80516020620048178339815191526200205584846200167f565b620020d7575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556200208c3390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505062000adc565b5f91505062000adc565b5f5f8051602062004817833981519152620020fd84846200167f565b15620020d7575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505062000adc565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805460011901620021a457604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b60608201355f620021bb8262001354565b90505f620021e5620021ce868062003a0a565b620021dd602089018962003a0a565b868962002e47565b9050846040013581146200221a5760408051630d2f5c5b60e41b815260048101839052908601356024820152604401620010ca565b6001546001600160a01b031663228951188562002238888062003a0a565b866200224860208c018c62003a0a565b886040518863ffffffff1660e01b81526004016200226c9695949392919062003b17565b5f604051808303818588803b15801562002284575f80fd5b505af115801562002297573d5f803e3d5ffd5b5050505050600a604051806040016040528087805f0190620022ba919062003a0a565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250938552505050606089013560209283015283546001810185559381522081519192600202019081906200231d908262003bba565b506020820151816001015550506008838154811062002340576200234062003975565b5f9182526020909120015460405163f99d9e5560e01b8152600481018690526001600160a01b039091169063f99d9e55906024015f604051808303815f87803b1580156200238c575f80fd5b505af11580156200239f573d5f803e3d5ffd5b505050507fdc72e80912284a6826bc0379c720da32bc7d9f8bef5caceb8c1408d7582e191983868060200190620023d7919062003a0a565b620023e3898062003a0a565b8688604051620023fa979695949392919062003c86565b60405180910390a15050505050565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6200243962003203565b565b6200244562003203565b620024396200324d565b6200245e602082018262003473565b6001600160a01b038116620024865760405163d92e233d60e01b815260040160405180910390fd5b62002498604083016020840162003473565b6001600160a01b038116620024c05760405163d92e233d60e01b815260040160405180910390fd5b620024d2606084016040850162003473565b6001600160a01b038116620024fa5760405163d92e233d60e01b815260040160405180910390fd5b6200250c60a085016080860162003473565b6001600160a01b038116620025345760405163d92e233d60e01b815260040160405180910390fd5b6200254660c0860160a0870162003473565b6001600160a01b0381166200256e5760405163d92e233d60e01b815260040160405180910390fd5b6200258060e0870160c0880162003473565b6001600160a01b038116620025a85760405163d92e233d60e01b815260040160405180910390fd5b620025bb610100880160e0890162003473565b6001600160a01b038116620025e35760405163d92e233d60e01b815260040160405180910390fd5b620025f75f62000eac60208b018b62003473565b506200261d5f80516020620047f783398151915262000eac60408b0160208c0162003473565b50620026557f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf75862000eac60808b0160608c0162003473565b506200268d7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d562000eac60a08b0160808c0162003473565b50620026c57ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c0638562000eac60608b0160408c0162003473565b50620026fd7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff508862000eac60c08b0160a08c0162003473565b50620027357f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a62000eac60e08b0160c08c0162003473565b506200276e7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a62000eac6101008b0160e08c0162003473565b505050505050505050565b6200278d6101808201610160830162003473565b6001600160a01b038116620027b55760405163d92e233d60e01b815260040160405180910390fd5b620027c96101a08301610180840162003473565b6001600160a01b038116620027f15760405163d92e233d60e01b815260040160405180910390fd5b620028056101c084016101a0850162003473565b6001600160a01b0381166200282d5760405163d92e233d60e01b815260040160405180910390fd5b620028416101e085016101c0860162003473565b6001600160a01b038116620028695760405163d92e233d60e01b815260040160405180910390fd5b6200287d6101808601610160870162003473565b600180546001600160a01b0319166001600160a01b0392909216919091179055620028b16101a08601610180870162003473565b5f80546001600160a01b0319166001600160a01b0392909216919091179055620028e46101c086016101a0870162003473565b600280546001600160a01b0319166001600160a01b0392909216919091179055620029186101e086016101c0870162003473565b600480546001600160a01b0319166001600160a01b03929092169190911790555050505050565b5f600183600181111562002957576200295762003ce0565b03620029dc5760075f9054906101000a90046001600160a01b03166001600160a01b031663453ba4886040518163ffffffff1660e01b8152600401602060405180830381865afa158015620029ae573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620029d4919062003aa3565b905062002a66565b5f836001811115620029f257620029f262003ce0565b0362002a495760075f9054906101000a90046001600160a01b03166001600160a01b0316631d9082886040518163ffffffff1660e01b8152600401602060405180830381865afa158015620029ae573d5f803e3d5ffd5b8260405163586dcbff60e01b8152600401620010ca919062003d15565b5f816001600160a01b0316836040515f6040518083038185875af1925050503d805f811462002ab1576040519150601f19603f3d011682016040523d82523d5f602084013e62002ab6565b606091505b505090508062002ad9576040516312171d8360e31b815260040160405180910390fd5b7f469ab84da5ea1952fdc0b8e86ec64bc27f9aa12eee7f41f7817fc282b13174fb858585604051620023fa9392919062003d25565b80516020820151604083015160608401515f8162002b2d848662003ad9565b62002b39919062003ad9565b90505f6008868154811062002b525762002b5262003975565b5f918252602090912001546040516379cb2d2f60e01b8152600481018490526001600160a01b03909116915081906379cb2d2f906024015f604051808303815f87803b15801562002ba1575f80fd5b505af115801562002bb4573d5f803e3d5ffd5b505050505f85111562002c285760065f9054906101000a90046001600160a01b03166001600160a01b0316631ef2aac5866040518263ffffffff1660e01b81526004015f604051808303818588803b15801562002c0f575f80fd5b505af115801562002c22573d5f803e3d5ffd5b50505050505b831562002caa57600c546040515f9161010090046001600160a01b03169086908381818185875af1925050503d805f811462002c80576040519150601f19603f3d011682016040523d82523d5f602084013e62002c85565b606091505b505090508062002ca8576040516312171d8360e31b815260040160405180910390fd5b505b821562002df657600754604080516308a7749160e31b815290515f926001600160a01b03169163453ba4889160048083019260209291908290030181865afa15801562002cf9573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062002d1f919062003aa3565b6001600160a01b0316846040515f6040518083038185875af1925050503d805f811462002d68576040519150601f19603f3d011682016040523d82523d5f602084013e62002d6d565b606091505b505090508062002d90576040516312171d8360e31b815260040160405180910390fd5b60075f9054906101000a90046001600160a01b03166001600160a01b031663f9fc0d076040518163ffffffff1660e01b81526004015f604051808303815f87803b15801562002ddd575f80fd5b505af115801562002df0573d5f803e3d5ffd5b50505050505b6040805187815260208101879052908101859052606081018490527f862fa866e2defc0ce54e39f7f30e76dc382859ffdaad1909d80281216e3a6ccd9060800160405180910390a150505050505050565b5f8062002e59633b9aca008462003d49565b90505f62002e678262003257565b90505f60028a8a5f60801b60405160200162002e869392919062003d69565b60408051601f198184030181529082905262002ea29162003d90565b602060405180830381855afa15801562002ebe573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002ee3919062003ac1565b90505f60028062002ef86040848c8e62003da3565b60405160200162002f0b92919062003a4f565b60408051601f198184030181529082905262002f279162003d90565b602060405180830381855afa15801562002f43573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002f68919062003ac1565b600262002f798b6040818f62003da3565b60405162002f8e9291905f9060200162003dcc565b60408051601f198184030181529082905262002faa9162003d90565b602060405180830381855afa15801562002fc6573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002feb919062003ac1565b60408051602081019390935282015260600160408051601f1981840301815290829052620030199162003d90565b602060405180830381855afa15801562003035573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906200305a919062003ac1565b905060028083896040516020016200307492919062003dde565b60408051601f1981840301815290829052620030909162003d90565b602060405180830381855afa158015620030ac573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190620030d1919062003ac1565b604051600290620030eb9087905f90879060200162003e05565b60408051601f1981840301815290829052620031079162003d90565b602060405180830381855afa15801562003123573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062003148919062003ac1565b60408051602081019390935282015260600160408051601f1981840301815290829052620031769162003d90565b602060405180830381855afa15801562003192573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190620031b7919062003ac1565b9b9a5050505050505050505050565b620031d282826200167f565b6200167b5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401620010ca565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166200243957604051631afcd79f60e31b815260040160405180910390fd5b6200240962003203565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b825f8151811062003299576200329962003975565b60200101906001600160f81b03191690815f1a9053508060061a60f81b82600181518110620032cc57620032cc62003975565b60200101906001600160f81b03191690815f1a9053508060051a60f81b82600281518110620032ff57620032ff62003975565b60200101906001600160f81b03191690815f1a9053508060041a60f81b8260038151811062003332576200333262003975565b60200101906001600160f81b03191690815f1a9053508060031a60f81b8260048151811062003365576200336562003975565b60200101906001600160f81b03191690815f1a9053508060021a60f81b8260058151811062003398576200339862003975565b60200101906001600160f81b03191690815f1a9053508060011a60f81b82600681518110620033cb57620033cb62003975565b60200101906001600160f81b03191690815f1a905350805f1a60f81b82600781518110620033fd57620033fd62003975565b60200101906001600160f81b03191690815f1a90535050919050565b6105978062003e3f83390190565b61042180620043d683390190565b5f6020828403121562003446575f80fd5b81356001600160e01b031981168114620013ec575f80fd5b6001600160a01b038116811462001e2d575f80fd5b5f6020828403121562003484575f80fd5b8135620013ec816200345e565b5f60208284031215620034a2575f80fd5b5035919050565b5f5b83811015620034c5578181015183820152602001620034ab565b50505f910152565b5f8151808452620034e6816020860160208601620034a9565b601f01601f19169290920160200192915050565b602081525f620013ec6020830184620034cd565b5f606082840312156200351f575f80fd5b50919050565b5f806040838503121562003537575f80fd5b8235915060208301356200354b816200345e565b809150509250929050565b604081525f6200356a6040830185620034cd565b90508260208301529392505050565b5f80602083850312156200358b575f80fd5b82356001600160401b0380821115620035a2575f80fd5b818501915085601f830112620035b6575f80fd5b813581811115620035c5575f80fd5b8660208260051b8501011115620035da575f80fd5b60209290920196919550909350505050565b5f6101e082840312156200351f575f80fd5b5f806040838503121562003610575f80fd5b823591506020830135600281106200354b575f80fd5b602080825282518282018190525f9190848201906040850190845b81811015620036685783516001600160a01b03168352928401929184019160010162003641565b50909695505050505050565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715620036ad57620036ad62003674565b60405290565b604051601f8201601f191681016001600160401b0381118282101715620036de57620036de62003674565b604052919050565b5f6020808385031215620036f8575f80fd5b82356001600160401b03808211156200370f575f80fd5b818501915085601f83011262003723575f80fd5b81358181111562003738576200373862003674565b62003748848260051b01620036b3565b818152848101925060079190911b83018401908782111562003768575f80fd5b928401925b81841015620019da576080848903121562003786575f80fd5b6200379062003688565b84358152858501358682015260408086013590820152606080860135908201528352608090930192918401916200376d565b5f8083601f840112620037d3575f80fd5b5081356001600160401b03811115620037ea575f80fd5b60208301915083602082850101111562003802575f80fd5b9250929050565b5f805f805f80608087890312156200381f575f80fd5b86356001600160401b038082111562003836575f80fd5b620038448a838b01620037c2565b90985096506020915088820135818111156200385e575f80fd5b6200386c8b828c01620037c2565b90975095505060408901358181111562003884575f80fd5b8901601f81018b1362003895575f80fd5b803582811115620038aa57620038aa62003674565b620038be601f8201601f19168501620036b3565b92508083528b84828401011115620038d4575f80fd5b80848301858501375f84828501015250508093505050606087013590509295509295509295565b5f60208083018184528085518083526040925060408601915060408160051b8701018488015f5b838110156200396757888303603f19018552815180518785526200394988860182620034cd565b91890151948901949094529487019492509086019060010162003922565b509098975050505050505050565b634e487b7160e01b5f52603260045260245ffd5b600181811c908216806200399e57607f821691505b6020821081036200351f57634e487b7160e01b5f52602260045260245ffd5b5f8235607e19833603018112620039d2575f80fd5b9190910192915050565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141762000adc5762000adc620039dc565b5f808335601e1984360301811262003a20575f80fd5b8301803591506001600160401b0382111562003a3a575f80fd5b60200191503681900382131562003802575f80fd5b818382375f9101908152919050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f62003a9b60208301848662003a5e565b949350505050565b5f6020828403121562003ab4575f80fd5b8151620013ec816200345e565b5f6020828403121562003ad2575f80fd5b5051919050565b8082018082111562000adc5762000adc620039dc565b5f6020828403121562003b00575f80fd5b81516001600160401b0381168114620013ec575f80fd5b608081525f62003b2c60808301888a62003a5e565b828103602084015262003b408188620034cd565b9050828103604084015262003b5781868862003a5e565b915050826060830152979650505050505050565b601f8211156200106757805f5260205f20601f840160051c8101602085101562003b925750805b601f840160051c820191505b8181101562003bb3575f815560010162003b9e565b5050505050565b81516001600160401b0381111562003bd65762003bd662003674565b62003bee8162003be7845462003989565b8462003b6b565b602080601f83116001811462003c24575f841562003c0c5750858301515b5f19600386901b1c1916600185901b17855562003c7e565b5f85815260208120601f198616915b8281101562003c545788860151825594840194600190910190840162003c33565b508582101562003c7257878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b87815260a060208201525f62003ca160a08301888a62003a5e565b828103604084015262003cb681878962003a5e565b9050846060840152828103608084015262003cd28185620034cd565b9a9950505050505050505050565b634e487b7160e01b5f52602160045260245ffd5b6002811062003d1157634e487b7160e01b5f52602160045260245ffd5b9052565b6020810162000adc828462003cf4565b8381526060810162003d3b602083018562003cf4565b826040830152949350505050565b5f8262003d6457634e487b7160e01b5f52601260045260245ffd5b500490565b828482376fffffffffffffffffffffffffffffffff19919091169101908152601001919050565b5f8251620039d2818460208701620034a9565b5f808585111562003db2575f80fd5b8386111562003dbf575f80fd5b5050820193919092039150565b82848237909101908152602001919050565b8281525f825162003df7816020850160208701620034a9565b919091016020019392505050565b5f845162003e18818460208901620034a9565b67ffffffffffffffff19949094169190930190815260188101919091526038019291505056fe60a060405260405161059738038061059783398101604081905261002291610376565b61002c828261003e565b506001600160a01b0316608052610465565b610047826100fb565b6040516001600160a01b038316907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e905f90a28051156100ef576100ea826001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100e49190610431565b82610209565b505050565b6100f761027c565b5050565b806001600160a01b03163b5f0361013557604051631933b43b60e21b81526001600160a01b03821660048201526024015b60405180910390fd5b807fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5080546001600160a01b0319166001600160a01b0392831617905560408051635c60da1b60e01b815290515f92841691635c60da1b9160048083019260209291908290030181865afa1580156101ae573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101d29190610431565b9050806001600160a01b03163b5f036100f757604051634c9c8ce360e01b81526001600160a01b038216600482015260240161012c565b60605f80846001600160a01b031684604051610225919061044a565b5f60405180830381855af49150503d805f811461025d576040519150601f19603f3d011682016040523d82523d5f602084013e610262565b606091505b50909250905061027385838361029d565b95945050505050565b341561029b5760405163b398979f60e01b815260040160405180910390fd5b565b6060826102b2576102ad826102fc565b6102f5565b81511580156102c957506001600160a01b0384163b155b156102f257604051639996b31560e01b81526001600160a01b038516600482015260240161012c565b50805b9392505050565b80511561030c5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b80516001600160a01b038116811461033b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b8381101561036e578181015183820152602001610356565b50505f910152565b5f8060408385031215610387575f80fd5b61039083610325565b60208401519092506001600160401b03808211156103ac575f80fd5b818501915085601f8301126103bf575f80fd5b8151818111156103d1576103d1610340565b604051601f8201601f19908116603f011681019083821181831017156103f9576103f9610340565b81604052828152886020848701011115610411575f80fd5b610422836020830160208801610354565b80955050505050509250929050565b5f60208284031215610441575f80fd5b6102f582610325565b5f825161045b818460208701610354565b9190910192915050565b60805161011b61047c5f395f601d015261011b5ff3fe6080604052600a600c565b005b60186014601a565b609d565b565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156076573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906098919060ba565b905090565b365f80375f80365f845af43d5f803e80801560b6573d5ff35b3d5ffd5b5f6020828403121560c9575f80fd5b81516001600160a01b038116811460de575f80fd5b939250505056fea26469706673582212202b8e47f9dbadb5e5b182ef67478f04e8f051fa0445601c532c82f3537586c39564736f6c63430008180033608060405234801561000f575f80fd5b5060405161042138038061042183398101604081905261002e9161015f565b806001600160a01b03811661005d57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61006681610077565b50610070826100c6565b5050610190565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b806001600160a01b03163b5f036100fb5760405163211eb15960e21b81526001600160a01b0382166004820152602401610054565b600180546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b80516001600160a01b038116811461015a575f80fd5b919050565b5f8060408385031215610170575f80fd5b61017983610144565b915061018760208401610144565b90509250929050565b6102848061019d5f395ff3fe608060405234801561000f575f80fd5b5060043610610055575f3560e01c80633659cfe6146100595780635c60da1b1461006e578063715018a6146100975780638da5cb5b1461009f578063f2fde38b146100af575b5f80fd5b61006c610067366004610221565b6100c2565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006c6100d6565b5f546001600160a01b031661007b565b61006c6100bd366004610221565b6100e9565b6100ca610128565b6100d381610154565b50565b6100de610128565b6100e75f6101d2565b565b6100f1610128565b6001600160a01b03811661011f57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100d3816101d2565b5f546001600160a01b031633146100e75760405163118cdaa760e01b8152336004820152602401610116565b806001600160a01b03163b5f036101895760405163211eb15960e21b81526001600160a01b0382166004820152602401610116565b600180546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208284031215610231575f80fd5b81356001600160a01b0381168114610247575f80fd5b939250505056fea26469706673582212205220934962511be7ea7a268c3ed5e444d6b1301ddf31d457c8b666be316f12a864736f6c6343000818003324791c44c040514a5d2580696fc45e7d3cb6c9fa65bf3db2e4755362d6c155b502dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a2646970667358221220ad669ff9dd3726d3637456fe0a46008f4ea459d5e8589ec96d59bd0b53e241ac64736f6c63430008180033", + "nonce": "0x1bd", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x0ca23b8da2816cb17b11c5b34ea668c645f45dd0246f6e1eae59a5b87819a19e", + "transactionType": "CREATE", + "contractName": "StakingNode", + "contractAddress": "0xf07861349ed0cb4603590b47d2269768ed6e2821", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x2df87e", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61286d80620000e35f395ff3fe60806040526004361061014a575f3560e01c806379cb2d2f116100b3578063b3c650151161006d578063b3c65015146103c5578063bfe8172d146103f1578063d609099314610405578063ef7953a21461041a578063f55b30401461042f578063f99d9e551461044e575f80fd5b806379cb2d2f1461030d578063852aa33d1461032057806388676cad1461034c5780639104c3191461036b57806392ab89bb14610392578063a3aae136146103a6575f80fd5b8063443dc42811610104578063443dc4281461026a5780635b4e128c146102885780635c60da1b146102a75780635d21e3da146102bb5780636e947298146102da57806377b34927146102ee575f80fd5b80630b10b201146101b8578063139d7fed146101e957806319259db01461020c5780631f1fb913146102215780632b72a3ca146102355780633f65cf1914610249575f80fd5b366101b4576001546001600160a01b0316331461017a576040516341e3874160e11b815260040160405180910390fd5b604080513381523460208201527fbfe611b001dfcd411432f7bf0d79b82b4b2ee81511edac123a3403c357fb972a910160405180910390a1005b5f80fd5b3480156101c3575f80fd5b506101cc610461565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101f4575f80fd5b506101fe60025481565b6040519081526020016101e0565b348015610217575f80fd5b506101fe60065481565b34801561022c575f80fd5b506006546101fe565b348015610240575f80fd5b506004546101fe565b348015610254575f80fd5b50610268610263366004611b52565b61064f565b005b348015610275575f80fd5b505f546101cc906001600160a01b031681565b348015610293575f80fd5b506102686102a2366004611c1f565b6108cf565b3480156102b2575f80fd5b506101cc6109b8565b3480156102c6575f80fd5b506102686102d5366004611ceb565b610a5a565b3480156102e5575f80fd5b506101fe610bfd565b3480156102f9575f80fd5b50610268610308366004611dc5565b610d12565b61026861031b366004611c1f565b610e5c565b34801561032b575f80fd5b5061033f61033a366004611c1f565b610f80565b6040516101e09190611e37565b348015610357575f80fd5b50610268610366366004611e5d565b611279565b348015610376575f80fd5b506101cc73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b34801561039d575f80fd5b5061026861135c565b3480156103b1575f80fd5b506001546101cc906001600160a01b031681565b3480156103d0575f80fd5b506103d9611565565b6040516001600160401b0390911681526020016101e0565b3480156103fc575f80fd5b506005546101fe565b348015610410575f80fd5b506101fe60055481565b348015610425575f80fd5b506101fe60045481565b34801561043a575f80fd5b50610268610449366004611f7e565b611589565b61026861045c366004611c1f565b611930565b5f61046a6119af565b6001546001600160a01b03161561048d57506001546001600160a01b0316610623565b5f805f9054906101000a90046001600160a01b03166001600160a01b0316634665bcda6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104dd573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050191906120ee565b9050806001600160a01b03166384d810626040518163ffffffff1660e01b81526004016020604051808303815f875af1158015610540573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056491906120ee565b5060405163a38406a360e01b81523060048201526001600160a01b0382169063a38406a390602401602060405180830381865afa1580156105a7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105cb91906120ee565b600180546001600160a01b0319166001600160a01b0392909216918217905560405130907fcdc82cfed67d9b46d3a15dd3b48745fb894a354d554cb5da5fb8c440f85c108e905f90a350506001546001600160a01b03165b61064c60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b90565b5f5460405163d02e02cf60e01b81523360048201526001600160a01b039091169063d02e02cf90602401602060405180830381865afa158015610694573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b89190612109565b6106d55760405163304ac09760e01b815260040160405180910390fd5b600154604051633f65cf1960e01b81526001600160a01b0390911690633f65cf1990610713908b908b908b908b908b908b908b908b906004016122c3565b5f604051808303815f87803b15801561072a575f80fd5b505af115801561073c573d5f803e3d5ffd5b505050505f5b858110156108c4575f6107a88484848181106107605761076061237b565b9050602002810190610772919061238f565b808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250611a1f92505050565b6001600160401b031690508787838181106107c5576107c561237b565b90506020020160208101906107da91906123d4565b604080516001600160401b038d1681526020810184905264ffffffffff92909216917fc893ff408bc759105622c2fb34c5fa34a9d25f376cae386441e090e9d1ce1c3e910160405180910390a26801bc16d674ec80000060055f8282546108419190612401565b9091555088905087838181106108595761085961237b565b905060200201602081019061086e91906123d4565b604080516001600160401b038d1681526020810184905264ffffffffff92909216917f95636f5f7adc353e75700ba4b3473fe8e0fdba943a168a06699b6e2a8c283d9a910160405180910390a250600101610742565b505050505050505050565b5f546001600160a01b031633146108f9576040516346f94cdd60e11b815260040160405180910390fd5b5f80516020612818833981519152805460029190600160401b900460ff168061092f575080546001600160401b03808416911610155b1561094d5760405163f92ee8a960e01b815260040160405180910390fd5b805468ffffffffffffffffff19166001600160401b038316908117600160401b1782556005849055815460ff60401b191682556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050565b5f806109e560017fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d51612401565b5f1b90505f815490505f819050806001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a2e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5291906120ee565b935050505090565b5f5460405163def8c18960e01b81523360048201526001600160a01b039091169063def8c18990602401602060405180830381865afa158015610a9f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ac39190612109565b610ae05760405163c97c994760e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5491906120ee565b60405163eea9064b60e01b81529091506001600160a01b0382169063eea9064b90610b8790879087908790600401612414565b5f604051808303815f87803b158015610b9e575f80fd5b505af1158015610bb0573d5f803e3d5ffd5b50505050836001600160a01b03167fa6ca69be1634c9486160d4fa9f11c9bf604a6a4b1fd23c8336ffc5889ef4b5ab83604051610bef91815260200190565b60405180910390a250505050565b5f805460408051632332de6d60e11b8152905183926001600160a01b031691634665bcda9160048083019260209291908290030181865afa158015610c44573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c6891906120ee565b6040516360f4062b60e01b81523060048201529091505f906001600160a01b038316906360f4062b90602401602060405180830381865afa158015610caf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cd3919061248b565b600654600554600454610ce691906124a2565b610cf091906124a2565b610cfa91906124b5565b90505f811215610d0c575f9250505090565b92915050565b80516001600160a01b038116610d3b5760405163d92e233d60e01b815260040160405180910390fd5b5f805160206128188339815191528054600160401b810460ff1615906001600160401b03165f81158015610d6c5750825b90505f826001600160401b03166001148015610d875750303b155b905081158015610d95575080155b15610db35760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610ddd57845460ff60401b1916600160401b1785555b610de5611aa5565b86515f80546001600160a01b0319166001600160a01b0390921691909117905560208701516002558315610e5357845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f546001600160a01b03163314610e86576040516346f94cdd60e11b815260040160405180910390fd5b600454811115610eb8576004805460405163d60a51ff60e01b8152918201839052602482015260440160405180910390fd5b6004546040805183815260208101929092527f2b349f43a65f38c08fc62d88fd2750e2ff318ddb2f862facc55cc4c98ac351d9910160405180910390a18060045f828254610f069190612401565b90915550505f80546040516001600160a01b039091169083908381818185875af1925050503d805f8114610f55576040519150601f19603f3d011682016040523d82523d5f602084013e610f5a565b606091505b5050905080610f7c576040516312171d8360e31b815260040160405180910390fd5b5050565b5f54604051630dc3e02960e01b81523360048201526060916001600160a01b031690630dc3e02990602401602060405180830381865afa158015610fc6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fea9190612109565b61100757604051639ed46c1f60e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611057573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107b91906120ee565b6040805160018082528183019092529192505f9190816020015b604080516060808201835280825260208201525f918101919091528152602001906001900390816110955750506040805160018082528183019092529192505f91906020808301908036833750506040805160018082528183019092529293505f9291506020808301908036833701905050905073beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0825f815181106111305761113061237b565b60200260200101906001600160a01b031690816001600160a01b03168152505085815f815181106111635761116361237b565b6020026020010181815250506040518060600160405280838152602001828152602001306001600160a01b0316815250835f815181106111a5576111a561237b565b60209081029190910101526040516306ec6e8160e11b81526001600160a01b03851690630dd8dd02906111dc908690600401612514565b5f604051808303815f875af11580156111f7573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261121e91908101906125a0565b94508560065f82825461123191906124a2565b90915550506040517f6001451f3352c7eb034f00744cc4a30b8a353c5b3a6115559440f7911435d87690611268908890889061262b565b60405180910390a150505050919050565b5f5460405163d02e02cf60e01b81523360048201526001600160a01b039091169063d02e02cf90602401602060405180830381865afa1580156112be573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112e29190612109565b6112ff5760405163304ac09760e01b815260040160405180910390fd5b6001546040516388676cad60e01b815282151560048201526001600160a01b03909116906388676cad906024015f604051808303815f87803b158015611343575f80fd5b505af1158015611355573d5f803e3d5ffd5b5050505050565b5f5460405163def8c18960e01b81523360048201526001600160a01b039091169063def8c18990602401602060405180830381865afa1580156113a1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113c59190612109565b6113e25760405163c97c994760e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061145691906120ee565b604051631976849960e21b81523060048201529091505f906001600160a01b038316906365da126490602401602060405180830381865afa15801561149d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114c191906120ee565b6040519091506001600160a01b038216907f42176493fdfcada70cc1bcf321c9a2314e9571a9fe53c54a5385a1eeac8bc1d7905f90a26040516336a2fa1960e21b81523060048201526001600160a01b0383169063da8be864906024015f604051808303815f875af1158015611539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261156091908101906125a0565b505050565b5f6115845f80516020612818833981519152546001600160401b031690565b905090565b5f54604051630dc3e02960e01b81523360048201526001600160a01b0390911690630dc3e02990602401602060405180830381865afa1580156115ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115f29190612109565b61160f57604051639ed46c1f60e01b815260040160405180910390fd5b5f8083516001600160401b0381111561162a5761162a611c5d565b604051908082528060200260200182016040528015611653578160200160208202803683370190505b5090505f84516001600160401b0381111561167057611670611c5d565b6040519080825280602002602001820160405280156116a357816020015b606081526020019060019003908161168e5790505b5090505f5b85518110156117d15760018382815181106116c5576116c561237b565b6020026020010190151590811515815250508581815181106116e9576116e961237b565b602002602001015160a00151516001600160401b0381111561170d5761170d611c5d565b604051908082528060200260200182016040528015611736578160200160208202803683370190505b508282815181106117495761174961237b565b60200260200101819052505f5b8682815181106117685761176861237b565b602002602001015160c00151518110156117c85786828151811061178e5761178e61237b565b602002602001015160c0015181815181106117ab576117ab61237b565b6020026020010151856117be91906124a2565b9450600101611756565b506001016116a8565b505f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611822573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061184691906120ee565b6040516319a021cb60e11b815290915047906001600160a01b0383169063334043969061187d908a9087908b908a90600401612738565b5f604051808303815f87803b158015611894575f80fd5b505af11580156118a6573d5f803e3d5ffd5b504792505f91506118b990508383612401565b90508660065f8282546118cc9190612401565b925050819055508060045f8282546118e491906124a2565b90915550506040517f8366d2488f13298ba3a5415b491f26008d1cf4fc377b05a30a26958d16288e099061191d908b908a9085906127f3565b60405180910390a1505050505050505050565b5f546001600160a01b0316331461195a576040516346f94cdd60e11b815260040160405180910390fd5b60055460408051918252602082018390527f0993f0703b1d00b4bfb9c89b808b7d29986b127f1d0e543c318f82957ac109f8910160405180910390a18060055f8282546119a791906124a2565b909155505050565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f008054600119016119f357604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b5f610d0c82600281518110611a3657611a3661237b565b602002602001015160f881901c60e882901c61ff00161760d882901c62ff0000161760c882901c63ff000000161764ff0000000060b883901c161765ff000000000060a883901c161766ff000000000000609883901c161767ff0000000000000060889290921c919091161790565b611aad611ab7565b611ab5611aed565b565b5f8051602061281883398151915254600160401b900460ff16611ab557604051631afcd79f60e31b815260040160405180910390fd5b6119f9611ab7565b5f60408284031215611b05575f80fd5b50919050565b5f8083601f840112611b1b575f80fd5b5081356001600160401b03811115611b31575f80fd5b6020830191508360208260051b8501011115611b4b575f80fd5b9250929050565b5f805f805f805f8060a0898b031215611b69575f80fd5b88356001600160401b038082168214611b80575f80fd5b90985060208a01359080821115611b95575f80fd5b611ba18c838d01611af5565b985060408b0135915080821115611bb6575f80fd5b611bc28c838d01611b0b565b909850965060608b0135915080821115611bda575f80fd5b611be68c838d01611b0b565b909650945060808b0135915080821115611bfe575f80fd5b50611c0b8b828c01611b0b565b999c989b5096995094979396929594505050565b5f60208284031215611c2f575f80fd5b5035919050565b6001600160a01b0381168114611c4a575f80fd5b50565b8035611c5881611c36565b919050565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715611c9357611c93611c5d565b60405290565b60405160e081016001600160401b0381118282101715611c9357611c93611c5d565b604051601f8201601f191681016001600160401b0381118282101715611ce357611ce3611c5d565b604052919050565b5f805f60608486031215611cfd575f80fd5b8335611d0881611c36565b92506020848101356001600160401b0380821115611d24575f80fd5b9086019060408289031215611d37575f80fd5b611d3f611c71565b823582811115611d4d575f80fd5b8301601f81018a13611d5d575f80fd5b803583811115611d6f57611d6f611c5d565b611d81601f8201601f19168701611cbb565b93508084528a86828401011115611d96575f80fd5b80868301878601375f908401860152509081529082013591810191909152929592945050506040919091013590565b5f60408284031215611dd5575f80fd5b611ddd611c71565b8235611de881611c36565b81526020928301359281019290925250919050565b5f815180845260208085019450602084015f5b83811015611e2c57815187529582019590820190600101611e10565b509495945050505050565b602081525f611e496020830184611dfd565b9392505050565b8015158114611c4a575f80fd5b5f60208284031215611e6d575f80fd5b8135611e4981611e50565b5f6001600160401b03821115611e9057611e90611c5d565b5060051b60200190565b803563ffffffff81168114611c58575f80fd5b5f82601f830112611ebc575f80fd5b81356020611ed1611ecc83611e78565b611cbb565b8083825260208201915060208460051b870101935086841115611ef2575f80fd5b602086015b84811015611f17578035611f0a81611c36565b8352918301918301611ef7565b509695505050505050565b5f82601f830112611f31575f80fd5b81356020611f41611ecc83611e78565b8083825260208201915060208460051b870101935086841115611f62575f80fd5b602086015b84811015611f175780358352918301918301611f67565b5f8060408385031215611f8f575f80fd5b82356001600160401b0380821115611fa5575f80fd5b818501915085601f830112611fb8575f80fd5b81356020611fc8611ecc83611e78565b82815260059290921b84018101918181019089841115611fe6575f80fd5b8286015b848110156120c157803586811115612000575f80fd5b870160e0818d03601f19011215612015575f80fd5b61201d611c99565b612028868301611c4d565b815261203660408301611c4d565b8682015261204660608301611c4d565b60408201526080820135606082015261206160a08301611e9a565b608082015260c08083013589811115612078575f80fd5b6120868f8983870101611ead565b60a08401525060e08301358981111561209d575f80fd5b6120ab8f8983870101611f22565b9183019190915250845250918301918301611fea565b50965050860135925050808211156120d7575f80fd5b506120e485828601611f22565b9150509250929050565b5f602082840312156120fe575f80fd5b8151611e4981611c36565b5f60208284031215612119575f80fd5b8151611e4981611e50565b5f808335601e19843603018112612139575f80fd5b83016020810192503590506001600160401b03811115612157575f80fd5b803603821315611b4b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b803564ffffffffff81168114611c58575f80fd5b5f838385526020808601955060208560051b830101845f5b878110156121f357848303601f190189526121d48288612124565b6121df858284612165565b9a86019a94505050908301906001016121b9565b5090979650505050505050565b8183525f6001600160fb1b03831115612217575f80fd5b8260051b80836020870137939093016020019392505050565b8183526020808401935f91600585811b8301820185855b888110156122b557858303601f19018a52813536899003601e1901811261226c575f80fd5b880185810190356001600160401b03811115612286575f80fd5b80861b3603821315612296575f80fd5b6122a1858284612200565b9b87019b9450505090840190600101612247565b509098975050505050505050565b6001600160401b03891681525f602060a06020840152893560a08401526122ed60208b018b612124565b604060c086015261230260e086018284612165565b85810360408701528a81528b925060200190505f5b8a8110156123415764ffffffffff61232e8461218d565b1682529183019190830190600101612317565b50848103606086015261235581898b6121a1565b92505050828103608084015261236c818587612230565b9b9a5050505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e198436030181126123a4575f80fd5b8301803591506001600160401b038211156123bd575f80fd5b6020019150600581901b3603821315611b4b575f80fd5b5f602082840312156123e4575f80fd5b611e498261218d565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610d0c57610d0c6123ed565b60018060a01b03841681525f60206060602084015284516040606085015280518060a08601525f5b818110156124585782810184015186820160c00152830161243c565b505f60c082870101526020870151608086015260c0601f19601f8301168601019350505050826040830152949350505050565b5f6020828403121561249b575f80fd5b5051919050565b80820180821115610d0c57610d0c6123ed565b8082018281125f8312801582168215821617156124d4576124d46123ed565b505092915050565b5f815180845260208085019450602084015f5b83811015611e2c5781516001600160a01b0316875295820195908201906001016124ef565b5f60208083018184528085518083526040925060408601915060408160051b8701018488015f5b838110156122b557603f19898403018552815160608151818652612561828701826124dc565b915050888201518582038a8701526125798282611dfd565b928901516001600160a01b031695890195909552509487019492509086019060010161253b565b5f60208083850312156125b1575f80fd5b82516001600160401b038111156125c6575f80fd5b8301601f810185136125d6575f80fd5b80516125e4611ecc82611e78565b81815260059190911b82018301908381019087831115612602575f80fd5b928401925b8284101561262057835182529284019290840190612607565b979650505050505050565b828152604060208201525f6126436040830184611dfd565b949350505050565b5f82825180855260208086019550808260051b8401018186015f5b848110156121f357858303601f19018952815180516001600160a01b03908116855285820151811686860152604080830151909116908501526060808201519085015260808082015163ffffffff169085015260a08082015160e082870181905291906126d5838801826124dc565b9250505060c080830151925085820381870152506126f38183611dfd565b9a86019a9450505090830190600101612666565b5f815180845260208085019450602084015f5b83811015611e2c57815115158752958201959082019060010161271a565b608081525f61274a608083018761264b565b6020838203818501528187518084528284019150828160051b850101838a015f5b838110156127c757868303601f190185528151805180855290870190878501905f5b818110156127b25783516001600160a01b03168352928901929189019160010161278d565b5050958701959350509085019060010161276b565b505086810360408801526127db818a611dfd565b94505050505082810360608401526126208185612707565b606081525f612805606083018661264b565b6020830194909452506040015291905056fef0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220b522996afc7ca4b85bbbfb8bec7d9486f91dd1577bd80271d51c9af514ec2fe364736f6c63430008180033", + "nonce": "0x1be", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3479f796160c2ee6003d1433b139439de226ff74a9556dd835a3fce5f519b340", + "transactionType": "CREATE", + "contractName": "ynETH", + "contractAddress": "0x090d67d3c97712f6c17a037515cbb8502561ee57", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x268aae", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b5061001861001d565b6100cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561006d5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100cc5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b6121a880620000dd5f395ff3fe608060405260043610610236575f3560e01c806361fd0ae111610129578063a9059cbb116100a8578063e63ab1e91161006d578063e63ab1e91461068e578063e7b0504b146106c1578063ef8b30f714610631578063f14210a6146106d6578063fb1bb9de146106f5575f80fd5b8063a9059cbb146105f3578063b7b199c714610612578063c6e6f59214610631578063d547741f14610650578063dd62ed3e1461066f575f80fd5b80638ffbf6db116100ee5780638ffbf6db1461054b57806391d148541461056a57806395d89b4114610589578063a1a25e6e1461059d578063a217fddf146105e0575f80fd5b806361fd0ae1146104bc578063631cbe3c146104db57806363d8882a146104e357806370a08231146104f75780638936a91f14610537575f80fd5b8063282c51f3116101b55780633f2a55401161017a5780633f2a55401461042857806342966c681461045f578063443dc4281461047e5780634cdad506146102e557806360da3e831461049c575f80fd5b8063282c51f3146103895780632d2da806146103bc5780632f2ff15d146103cf578063313ce567146103ee57806336568abe14610409575f80fd5b8063095ea7b3116101fb578063095ea7b31461030457806318160ddd146103235780631ef2aac51461034357806323b872dd1461034b578063248a9ca31461036a575f80fd5b806301e1d1141461025857806301ffc9a71461027f57806302191980146102ae57806306fdde03146102c457806307a2d13a146102e5575f80fd5b3661025457604051636c897afd60e01b815260040160405180910390fd5b5f80fd5b348015610263575f80fd5b5061026c610715565b6040519081526020015b60405180910390f35b34801561028a575f80fd5b5061029e610299366004611b60565b610740565b6040519015158152602001610276565b3480156102b9575f80fd5b506102c2610774565b005b3480156102cf575f80fd5b506102d86107f8565b6040516102769190611b87565b3480156102f0575f80fd5b5061026c6102ff366004611bd3565b6108b8565b34801561030f575f80fd5b5061029e61031e366004611bfe565b6108c3565b34801561032e575f80fd5b505f805160206121138339815191525461026c565b6102c26108da565b348015610356575f80fd5b5061029e610365366004611c28565b6109ef565b348015610375575f80fd5b5061026c610384366004611bd3565b610a14565b348015610394575f80fd5b5061026c7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84881565b61026c6103ca366004611c66565b610a34565b3480156103da575f80fd5b506102c26103e9366004611c81565b610b23565b3480156103f9575f80fd5b5060405160128152602001610276565b348015610414575f80fd5b506102c2610423366004611c81565b610b45565b348015610433575f80fd5b50600154610447906001600160a01b031681565b6040516001600160a01b039091168152602001610276565b34801561046a575f80fd5b506102c2610479366004611bd3565b610b7d565b348015610489575f80fd5b505f54610447906001600160a01b031681565b3480156104a7575f80fd5b5060015461029e90600160a01b900460ff1681565b3480156104c7575f80fd5b506102c26104d6366004611d90565b610bb5565b6102c2610bd6565b3480156104ee575f80fd5b506102c2610c53565b348015610502575f80fd5b5061026c610511366004611c66565b6001600160a01b03165f9081525f805160206120f3833981519152602052604090205490565b348015610542575f80fd5b506102c2610cb7565b348015610556575f80fd5b506102c2610565366004611d90565b610d32565b348015610575575f80fd5b5061029e610584366004611c81565b610d54565b348015610594575f80fd5b506102d8610d8a565b3480156105a8575f80fd5b5061029e6105b7366004611c66565b6001600160a01b03165f9081525f805160206120d3833981519152602052604090205460ff1690565b3480156105eb575f80fd5b5061026c5f81565b3480156105fe575f80fd5b5061029e61060d366004611bfe565b610dc8565b34801561061d575f80fd5b506102c261062c366004611dd2565b610dd5565b34801561063c575f80fd5b5061026c61064b366004611bd3565b6110cd565b34801561065b575f80fd5b506102c261066a366004611c81565b6110d8565b34801561067a575f80fd5b5061026c610689366004611e95565b6110f4565b348015610699575f80fd5b5061026c7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b3480156106cc575f80fd5b5061026c60025481565b3480156106e1575f80fd5b506102c26106f0366004611bd3565b61113d565b348015610700575f80fd5b5061026c5f8051602061213383398151915281565b6002545f9081906107269082611ed5565b9050610730611264565b61073a9082611ed5565b92915050565b5f6001600160e01b03198216637965db0b60e01b148061073a57506301ffc9a760e01b6001600160e01b031983161461073a565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a61079e816112dd565b6001805460ff60a01b1916600160a01b908117918290556040517f35e03641f0c63f166bca10b4ee931a3c02be26ffb29a395ce012099d99ecb6cc926107ed92900460ff161515815260200190565b60405180910390a150565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0380546060915f805160206120f38339815191529161083690611ee8565b80601f016020809104026020016040519081016040528092919081815260200182805461086290611ee8565b80156108ad5780601f10610884576101008083540402835291602001916108ad565b820191905f5260205f20905b81548152906001019060200180831161089057829003601f168201915b505050505091505090565b5f61073a825f6112ea565b5f336108d081858561132e565b5060019392505050565b5f546001600160a01b031633148061097557505f8054906101000a90046001600160a01b03166001600160a01b0316637d1f8a816040518163ffffffff1660e01b81526004016020604051808303815f875af115801561093c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109609190611f20565b6001600160a01b0316336001600160a01b0316145b61099957604051631bb2e8ef60e21b81523360048201526024015b60405180910390fd5b3460025f8282546109aa9190611ed5565b90915550506002546040805134815260208101929092527f7be0d7fd798145f666ec52ac3f0d58a38be2081e9debf17f96e11c21c848ea2791015b60405180910390a1565b5f336109fc85828561133b565b610a07858585611398565b60019150505b9392505050565b5f9081525f80516020612153833981519152602052604090206001015490565b6001545f90600160a01b900460ff1615610a61576040516313d0ff5960e31b815260040160405180910390fd5b345f03610a81576040516335ed7e5760e11b815260040160405180910390fd5b34610a8b816110cd565b9150815f03610aad57604051639811e0c760e01b815260040160405180910390fd5b610ab783836113f5565b8060025f828254610ac89190611ed5565b9091555050600254604080518381526020810185905280820192909252516001600160a01b0385169133917f4e2ca0515ed1aef1395f66b5303bb5d6f1bf9d61a353fa53f73f8ac9973fa9f69181900360600190a350919050565b610b2c82610a14565b610b35816112dd565b610b3f8383611429565b50505050565b6001600160a01b0381163314610b6e5760405163334bd91960e11b815260040160405180910390fd5b610b7882826114ca565b505050565b7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848610ba7816112dd565b610bb13383611543565b5050565b5f80516020612133833981519152610bcc816112dd565b610bb1825f611577565b6001546001600160a01b03163314610c035760405163df6752fd60e01b8152336004820152602401610990565b3460025f828254610c149190611ed5565b90915550506002546040805134815260208101929092527f61cb44cbea389abb97c617c7d16a62235c51f27da3406d3ec3c9ac87c0d0c82691016109e5565b5f80516020612133833981519152610c6a816112dd565b6001805460ff60a01b1916908190556040517f35e03641f0c63f166bca10b4ee931a3c02be26ffb29a395ce012099d99ecb6cc916107ed91600160a01b90910460ff161515815260200190565b5f80516020612133833981519152610cce816112dd565b7f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201805460ff191690556040515f805160206120d3833981519152907f5b1b646968864ea75fad546d8c819e0de710df0bf61f658f6ba54dc587472e7b905f90a15050565b5f80516020612133833981519152610d49816112dd565b610bb1826001611577565b5f9182525f80516020612153833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f805160206120f38339815191529161083690611ee8565b5f336108d0818585611398565b80516001600160a01b038116610dfe5760405163d92e233d60e01b815260040160405180910390fd5b60208201516001600160a01b038116610e2a5760405163d92e233d60e01b815260040160405180910390fd5b60408301516001600160a01b038116610e565760405163d92e233d60e01b815260040160405180910390fd5b60608401516001600160a01b038116610e825760405163d92e233d60e01b815260040160405180910390fd5b60808501516001600160a01b038116610eae5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f81158015610ef35750825b90505f8267ffffffffffffffff166001148015610f0f5750303b155b905081158015610f1d575080155b15610f3b5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610f6557845460ff60401b1916600160401b1785555b610f6d611612565b610fb1604051806040016040528060058152602001640f2dc8aa8960db1b815250604051806040016040528060058152602001640f2dc8aa8960db1b81525061161c565b8a51610fbe905f90611429565b50610fed7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8c60200151611429565b506110095f805160206121338339815191528c60400151611429565b5060608b01515f80546001600160a01b039283166001600160a01b03199182161790915560808d0151600180549190931691161781557f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201805460ff1916909117905561107a8b60a001516001611577565b83156110c057845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050505050565b5f61073a825f61162e565b6110e182610a14565b6110ea816112dd565b610b3f83836114ca565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b5f546001600160a01b0316331461117b575f5460405163d95eb40f60e01b81526001600160a01b039091166004820152336024820152604401610990565b6002548181101561119f57604051631e9acf1760e31b815260040160405180910390fd5b5f6111aa8383611f3b565b60028190555f805460405192935090916001600160a01b039091169085908381818185875af1925050503d805f81146111fe576040519150601f19603f3d011682016040523d82523d5f602084013e611203565b606091505b5050905080611225576040516312171d8360e31b815260040160405180910390fd5b60408051858152602081018490527f72e1ec2b6bc1315b9dd8b8a5704a77cbac7378d6e44866f92d6985f0793dc4c2910160405180910390a150505050565b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ff50abdc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d89190611f4e565b905090565b6112e78133611678565b50565b5f806113015f805160206121138339815191525490565b9050805f03611313578391505061073a565b6113268461131f610715565b83866116b1565b949350505050565b610b7883838360016116fc565b5f61134684846110f4565b90505f198114610b3f578181101561138a57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610990565b610b3f84848484035f6116fc565b6001600160a01b0383166113c157604051634b637e8f60e11b81525f6004820152602401610990565b6001600160a01b0382166113ea5760405163ec442f0560e01b81525f6004820152602401610990565b610b788383836117e0565b6001600160a01b03821661141e5760405163ec442f0560e01b81525f6004820152602401610990565b610bb15f83836117e0565b5f5f805160206121538339815191526114428484610d54565b6114c1575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556114773390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061073a565b5f91505061073a565b5f5f805160206121538339815191526114e38484610d54565b156114c1575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061073a565b6001600160a01b03821661156c57604051634b637e8f60e11b81525f6004820152602401610990565b610bb1825f836117e0565b5f805160206120d38339815191525f5b8351811015610b3f575f8482815181106115a3576115a3611f65565b6020908102919091018101516001600160a01b0381165f818152868452604090819020805460ff19168915159081179091559051908152919350917fac6d52e87f8a8fc486e04600668caffa28c149f8286b136c0f327b4499b8c723910160405180910390a250600101611587565b61161a611889565b565b611624611889565b610bb182826118d2565b5f6116445f805160206121138339815191525490565b5f0361165157508161073a565b610a0d8361166a5f805160206121138339815191525490565b611672610715565b856116b1565b6116828282610d54565b610bb15760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610990565b5f6116de6116be836118e4565b80156116d957505f84806116d4576116d4611f79565b868809115b151590565b6116e9868686611910565b6116f39190611ed5565b95945050505050565b5f805160206120f38339815191526001600160a01b0385166117335760405163e602df0560e01b81525f6004820152602401610990565b6001600160a01b03841661175c57604051634a1406b160e11b81525f6004820152602401610990565b6001600160a01b038086165f908152600183016020908152604080832093881683529290522083905581156117d957836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040516117d091815260200190565b60405180910390a35b5050505050565b7f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201545f805160206120d38339815191529060ff16801561183857506001600160a01b0384165f9081526020829052604090205460ff16155b801561184c57506001600160a01b03841615155b801561186057506001600160a01b03831615155b1561187e57604051636ab17e0560e01b815260040160405180910390fd5b610b3f8484846119c6565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661161a57604051631afcd79f60e31b815260040160405180910390fd5b6118da611889565b610bb18282611aff565b5f60028260038111156118f9576118f9611f8d565b6119039190611fa1565b60ff166001149050919050565b5f838302815f1985870982811083820303915050805f036119445783828161193a5761193a611f79565b0492505050610a0d565b80841161195b5761195b6003851502601118611b4f565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f805160206120f38339815191526001600160a01b038416611a005781816002015f8282546119f59190611ed5565b90915550611a709050565b6001600160a01b0384165f9081526020829052604090205482811015611a525760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610990565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316611a8e576002810180548390039055611aac565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051611af191815260200190565b60405180910390a350505050565b611b07611889565b5f805160206120f38339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace03611b408482612012565b5060048101610b3f8382612012565b634e487b715f52806020526024601cfd5b5f60208284031215611b70575f80fd5b81356001600160e01b031981168114610a0d575f80fd5b5f602080835283518060208501525f5b81811015611bb357858101830151858201604001528201611b97565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215611be3575f80fd5b5035919050565b6001600160a01b03811681146112e7575f80fd5b5f8060408385031215611c0f575f80fd5b8235611c1a81611bea565b946020939093013593505050565b5f805f60608486031215611c3a575f80fd5b8335611c4581611bea565b92506020840135611c5581611bea565b929592945050506040919091013590565b5f60208284031215611c76575f80fd5b8135610a0d81611bea565b5f8060408385031215611c92575f80fd5b823591506020830135611ca481611bea565b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715611ce657611ce6611caf565b60405290565b5f82601f830112611cfb575f80fd5b8135602067ffffffffffffffff80831115611d1857611d18611caf565b8260051b604051601f19603f83011681018181108482111715611d3d57611d3d611caf565b6040529384526020818701810194908101925087851115611d5c575f80fd5b6020870191505b84821015611d85578135611d7681611bea565b83529183019190830190611d63565b979650505050505050565b5f60208284031215611da0575f80fd5b813567ffffffffffffffff811115611db6575f80fd5b61132684828501611cec565b8035611dcd81611bea565b919050565b5f60208284031215611de2575f80fd5b813567ffffffffffffffff80821115611df9575f80fd5b9083019060c08286031215611e0c575f80fd5b611e14611cc3565b8235611e1f81611bea565b81526020830135611e2f81611bea565b60208201526040830135611e4281611bea565b6040820152611e5360608401611dc2565b6060820152611e6460808401611dc2565b608082015260a083013582811115611e7a575f80fd5b611e8687828601611cec565b60a08301525095945050505050565b5f8060408385031215611ea6575f80fd5b8235611eb181611bea565b91506020830135611ca481611bea565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561073a5761073a611ec1565b600181811c90821680611efc57607f821691505b602082108103611f1a57634e487b7160e01b5f52602260045260245ffd5b50919050565b5f60208284031215611f30575f80fd5b8151610a0d81611bea565b8181038181111561073a5761073a611ec1565b5f60208284031215611f5e575f80fd5b5051919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b5f60ff831680611fbf57634e487b7160e01b5f52601260045260245ffd5b8060ff84160691505092915050565b601f821115610b7857805f5260205f20601f840160051c81016020851015611ff35750805b601f840160051c820191505b818110156117d9575f8155600101611fff565b815167ffffffffffffffff81111561202c5761202c611caf565b6120408161203a8454611ee8565b84611fce565b602080601f831160018114612073575f841561205c5750858301515b5f19600386901b1c1916600185901b1785556120ca565b5f85815260208120601f198616915b828110156120a157888601518255948401946001909101908401612082565b50858210156120be57878501515f19600388901b60f8161c191681555b505060018460011b0185555b50505050505056fe7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba305320052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a264697066735822122034c087a61bb674f4958a98f5cf1bd52d2f584c4c96bab73fa37add66d2ce63c364736f6c63430008180033", + "nonce": "0x1bf", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e4d1c3b95813e51405e6ba50a914aaa57f505916bb93d6f75df78b2a6023fe2", + "transactionType": "CREATE", + "contractName": "ynETHRedemptionAssetsVault", + "contractAddress": "0x7ebe6eac9ad9b5e4d637c8f05191b273b009461a", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x117065", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b50610eeb8061001d5f395ff3fe608060405260043610610113575f3560e01c80638456cb591161009d578063d26b3e2611610062578063d26b3e261461031d578063d2edb68f1461033c578063d547741f1461035b578063e63ab1e91461037a578063fb1bb9de146103ad575f80fd5b80638456cb59146102a757806391d14854146102bb5780639872ab84146102da578063a217fddf146102f8578063acd08aa71461030b575f80fd5b806336568abe116100e357806336568abe146102225780633f4ba83a14610241578063540385a3146102555780635c62c822146102695780635c975abb14610288575f80fd5b806301ffc9a714610169578063248a9ca31461019d5780632ba29d38146101ca5780632f2ff15d14610201575f80fd5b366101655760405134815273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9033907f34a31c07280ba0d3e5c883a4468da49d04e80d542a11e74c91fa4aa96a3c96da9060200160405180910390a3005b5f80fd5b348015610174575f80fd5b50610188610183366004610cf9565b6103e0565b60405190151581526020015b60405180910390f35b3480156101a8575f80fd5b506101bc6101b7366004610d27565b610416565b604051908152602001610194565b3480156101d5575f80fd5b506001546101e9906001600160a01b031681565b6040516001600160a01b039091168152602001610194565b34801561020c575f80fd5b5061022061021b366004610d52565b610436565b005b34801561022d575f80fd5b5061022061023c366004610d52565b610458565b34801561024c575f80fd5b50610220610490565b348015610260575f80fd5b506101bc6104c9565b348015610274575f80fd5b50610220610283366004610d27565b610540565b348015610293575f80fd5b505f5461018890600160a01b900460ff1681565b3480156102b2575f80fd5b5061022061068b565b3480156102c6575f80fd5b506101886102d5366004610d52565b6106ca565b3480156102e5575f80fd5b505f546101e9906001600160a01b031681565b348015610303575f80fd5b506101bc5f81565b348015610316575f80fd5b50476101bc565b348015610328575f80fd5b50610220610337366004610d80565b610700565b348015610347575f80fd5b50610220610356366004610dfc565b610937565b348015610366575f80fd5b50610220610375366004610d52565b610adf565b348015610385575f80fd5b506101bc7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b3480156103b8575f80fd5b506101bc7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b5f6001600160e01b03198216637965db0b60e01b148061041057506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f9081525f80516020610e96833981519152602052604090206001015490565b61043f82610416565b61044881610afb565b6104528383610b05565b50505050565b6001600160a01b03811633146104815760405163334bd91960e11b815260040160405180910390fd5b61048b8282610ba6565b505050565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a6104ba81610afb565b505f805460ff60a01b19169055565b5f805460405163266d6a8360e11b8152670de0b6b3a764000060048201526001600160a01b0390911690634cdad50690602401602060405180830381865afa158015610517573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061053b9190610e7e565b905090565b6001546001600160a01b0316331461057257604051630bde7ac360e11b81523360048201526024015b60405180910390fd5b5f54600160a01b900460ff161561059c5760405163ab35696f60e01b815260040160405180910390fd5b6105a4610c1f565b5f8054906101000a90046001600160a01b03166001600160a01b0316631ef2aac5826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156105ef575f80fd5b505af1158015610601573d5f803e3d5ffd5b50505f546040518581526001600160a01b03909116935033925073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee91507f10964f2d145c29d7c7688147486387f37f033564029b447ab7c0983f292acc789060200160405180910390a461068860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6106b581610afb565b505f805460ff60a01b1916600160a01b179055565b5f9182525f80516020610e96833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b80516001600160a01b0381166107295760405163d92e233d60e01b815260040160405180910390fd5b60208201516001600160a01b0381166107555760405163d92e233d60e01b815260040160405180910390fd5b60408301516001600160a01b0381166107815760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156107c65750825b90505f8267ffffffffffffffff1660011480156107e25750303b155b9050811580156107f0575080155b1561080e5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561083857845460ff60401b1916600160401b1785555b610840610c69565b885161084d905f90610b05565b5061087b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8a5f0151610b05565b506108a97f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a8a5f0151610b05565b506020890151600180546001600160a01b039283166001600160a01b031990911617905560408a01515f80546001600160a81b03191691909216179055831561092c57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050505050565b6001546001600160a01b0316331461096457604051630bde7ac360e11b8152336004820152602401610569565b5f54600160a01b900460ff161561098e5760405163ab35696f60e01b815260040160405180910390fd5b610996610c1f565b47838110156109dc5760405163a8c6953f60e01b815273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee60048201526024810185905260448101829052606401610569565b5f856001600160a01b0316856040515f6040518083038185875af1925050503d805f8114610a25576040519150601f19603f3d011682016040523d82523d5f602084013e610a2a565b606091505b5050905080610a5e5760405163f68179b160e01b8152600481018690526001600160a01b0387166024820152604401610569565b6040518581526001600160a01b03871690339073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee907f25137ec1c481240ec1a0e55c3c8c5f5e3467c1a66454f84fb39c963378cc0e779060200160405180910390a4505061045260017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b610ae882610416565b610af181610afb565b6104528383610ba6565b6106888133610c73565b5f5f80516020610e96833981519152610b1e84846106ca565b610b9d575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610b533390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a46001915050610410565b5f915050610410565b5f5f80516020610e96833981519152610bbf84846106ca565b15610b9d575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a46001915050610410565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805460011901610c6357604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b610c71610cb0565b565b610c7d82826106ca565b610cac5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610569565b5050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610c7157604051631afcd79f60e31b815260040160405180910390fd5b5f60208284031215610d09575f80fd5b81356001600160e01b031981168114610d20575f80fd5b9392505050565b5f60208284031215610d37575f80fd5b5035919050565b6001600160a01b0381168114610688575f80fd5b5f8060408385031215610d63575f80fd5b823591506020830135610d7581610d3e565b809150509250929050565b5f60608284031215610d90575f80fd5b6040516060810181811067ffffffffffffffff82111715610dbf57634e487b7160e01b5f52604160045260245ffd5b6040528235610dcd81610d3e565b81526020830135610ddd81610d3e565b60208201526040830135610df081610d3e565b60408201529392505050565b5f805f8060608587031215610e0f575f80fd5b8435610e1a81610d3e565b935060208501359250604085013567ffffffffffffffff80821115610e3d575f80fd5b818701915087601f830112610e50575f80fd5b813581811115610e5e575f80fd5b886020828501011115610e6f575f80fd5b95989497505060200194505050565b5f60208284031215610e8e575f80fd5b505191905056fe02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212208d27b179d865bd32c94fae6a09a9a953118f6745b55ba84f343c1cd38020a92f64736f6c63430008180033", + "nonce": "0x1c0", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xfbf7b311265081a42e6e5e727d2cb34c5016583d1c733acbb3ad7fdf5ef5929a", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "function": null, + "arguments": [ + "0x7eBE6EAC9AD9B5e4D637c8f05191b273b009461a", + "0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913", + "0x" + ], + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61030000000000000000000000007ebe6eac9ad9b5e4d637c8f05191b273b009461a000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b91300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x1c1", + "chainId": "0x4268" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0xd4d8e80d1c959cdc4601cc6a0cc1ec18abeb1888", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + } + ], + "isFixedGasLimit": false + }, + { + "hash": "0x1c9967ffe3fed1052a8922862e82456bf6511c1bab6c97fd9940aaec95993442", + "transactionType": "CREATE", + "contractName": "WithdrawalQueueManager", + "contractAddress": "0x28732d8061d35b77dc2997926e0449dadc3ef3dd", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0x475d45", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b613f8b80620000e35f395ff3fe608060405234801561000f575f80fd5b5060043610610388575f3560e01c80637d1f8a81116101df578063ac1e502511610109578063dbbfd152116100a9578063e6dc4f1611610079578063e6dc4f1614610824578063e985e9c514610837578063efdcd9741461084a578063f84694e11461085d575f80fd5b8063dbbfd152146107f6578063e26dc432146107fe578063e51aa67814610811578063e63a391f1461081a575f80fd5b8063b88d4fde116100e4578063b88d4fde14610771578063bbeea20b14610784578063c87b56dd146107d0578063d547741f146107e3575f80fd5b8063ac1e502514610743578063b3f0067414610756578063b6dddc8314610769575f80fd5b80639c69c3471161017f578063a08ab5c31161014f578063a08ab5c314610702578063a217fddf14610715578063a22cb4651461071c578063aae733581461072f575f80fd5b80639c69c347146106a25780639dea569d146106b55780639ee679e8146106c8578063a024d723146106db575f80fd5b80638c4724cc116101ba5780638c4724cc1461065a57806391d1485414610662578063937b25811461067557806395d89b411461069a575f80fd5b80637d1f8a811461063557806384c4bd4b146106485780638bc7e8c414610651575f80fd5b80632f745c59116102c05780634718d52711610260578063612c1fa011610230578063612c1fa0146105d65780636352211e146105fd57806370a0823114610610578063748f9ef014610623575f80fd5b80634718d527146105405780634bac05a9146105825780634f6ccce7146105a35780635209cb98146105b6575f80fd5b80633569d58a1161029b5780633569d58a1461050757806336568abe146105105780633dd31e0e1461052357806342842e0e1461052d575f80fd5b80632f745c59146104ce5780633384d9fb146104e157806334e73122146104f4575f80fd5b80630b7019d91161032b57806318160ddd1161030657806318160ddd1461048157806323b872dd14610495578063248a9ca3146104a85780632f2ff15d146104bb575f80fd5b80630b7019d9146104445780630cc2250f1461045b578063115949211461046e575f80fd5b80630770fa91116103665780630770fa91146103de578063081812fc146103f35780630913b1211461041e578063095ea7b314610431575f80fd5b806301ffc9a71461038c57806306fdde03146103b4578063071fc512146103c9575b5f80fd5b61039f61039a366004613384565b610870565b60405190151581526020015b60405180910390f35b6103bc61089a565b6040516103ab91906133ec565b61039f6103d73660046133fe565b6009541190565b6103f16103ec3660046133fe565b61093b565b005b6104066104013660046133fe565b610a38565b6040516001600160a01b0390911681526020016103ab565b6103f161042c36600461345c565b610a4c565b6103f161043f3660046134e1565b610ad2565b61044d60045481565b6040519081526020016103ab565b6103f16104693660046135d0565b610ae1565b6103f161047c3660046136e4565b610df2565b5f80516020613ed68339815191525461044d565b6103f16104a3366004613743565b610e1c565b61044d6104b63660046133fe565b610ea5565b6103f16104c9366004613781565b610ec5565b61044d6104dc3660046134e1565b610ee1565b6103f16104ef366004613781565b610f52565b61044d6105023660046137af565b610fa9565b61044d60095481565b6103f161051e366004613781565b610fc9565b61044d6224ea0081565b6103f161053b366004613743565b611001565b61055361054e3660046133fe565b61101b565b604080516001600160401b0394851681529390921660208401526001600160601b0316908201526060016103ab565b6105956105903660046137cf565b61105e565b6040516103ab929190613832565b61044d6105b13660046133fe565b611294565b6105c96105c43660046133fe565b611306565b6040516103ab91906138c8565b61044d7f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e81565b61040661060b3660046133fe565b611427565b61044d61061e3660046137cf565b611431565b5f54610406906001600160a01b031681565b600154610406906001600160a01b031681565b61044d60025481565b61044d60055481565b61044d611489565b61039f610670366004613781565b611526565b6106886106833660046133fe565b61155c565b6040516103ab969594939291906138da565b6103bc61161f565b61044d6106b03660046137af565b61165d565b600754610406906001600160a01b031681565b61044d6106d63660046133fe565b6116e6565b61044d7f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead9581565b61044d610710366004613918565b6116fb565b61044d5f81565b6103f161072a36600461395b565b6118cb565b61044d5f80516020613f3683398151915281565b6103f16107513660046133fe565b6118d6565b600654610406906001600160a01b031681565b600a5461044d565b6103f161077f36600461398b565b61194d565b6107976107923660046133fe565b611965565b6040805182516001600160401b03908116825260208085015190911690820152918101516001600160601b0316908201526060016103ab565b6103bc6107de3660046133fe565b611a0b565b6103f16107f1366004613781565b611a7b565b61044d611a97565b6103f161080c3660046139f2565b611b26565b61044d60085481565b61044d620f424081565b61044d6108323660046133fe565b611b6d565b61039f610845366004613a60565b611da5565b6103f16108583660046137cf565b611df1565b61044d61086b3660046133fe565b611e8d565b5f6001600160e01b031982166380ac58cd60e01b1480610894575061089482611fc6565b92915050565b5f80516020613eb683398151915280546060919081906108b990613a8c565b80601f01602080910402602001604051908101604052809291908181526020018280546108e590613a8c565b80156109305780601f1061090757610100808354040283529160200191610930565b820191905f5260205f20905b81548152906001019060200180831161091357829003601f168201915b505050505091505090565b7f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e61096581611fea565b5f61096e611a97565b9050808311156109a057604051630bf0035360e31b815260048101849052602481018290526044015b60405180910390fd5b600154604051632e31641160e11b8152600481018590526001600160a01b0390911690635c62c822906024015f604051808303815f87803b1580156109e3575f80fd5b505af11580156109f5573d5f803e3d5ffd5b505060408051868152602081018590527f5471a21865bef29e2f9817eeb3138f749523b6e76a3c880a65fdd61d35604ac8935001905060405180910390a1505050565b5f610a4282611ff4565b506108948261202b565b828114610a7657604051631f4bb7c160e31b81526004810184905260248101829052604401610997565b5f5b83811015610acb57610ac3858583818110610a9557610a95613ac4565b90506020020135848484818110610aae57610aae613ac4565b90506020020160208101906104ef91906137cf565b600101610a78565b5050505050565b610add828233612064565b5050565b60808101516001600160a01b038116610b0d5760405163d92e233d60e01b815260040160405180910390fd5b60408201516001600160a01b038116610b395760405163d92e233d60e01b815260040160405180910390fd5b60608301516001600160a01b038116610b655760405163d92e233d60e01b815260040160405180910390fd5b60a08401516001600160a01b038116610b915760405163d92e233d60e01b815260040160405180910390fd5b6101008501516001600160a01b038116610bbe5760405163d92e233d60e01b815260040160405180910390fd5b6101208601516001600160a01b038116610beb5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f81158015610c2f5750825b90505f826001600160401b03166001148015610c4a5750303b155b905081158015610c58575080155b15610c765760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610ca057845460ff60401b1916600160401b1785555b610cb18c5f01518d60200151612071565b60408c01515f80546001600160a01b039283166001600160a01b031991821617825560608f0151600180549190941691161790915560808d0151610cf59190612083565b50610d115f80516020613f368339815191528d60a00151612083565b50610d407f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e8d60c00151612083565b50610d707f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead958d6101200151612083565b5060e08c01516005556101008c0151600680546001600160a01b0319166001600160a01b039092169190911790558315610de457845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050505050505050565b610dfa612124565b610e038161215b565b610e1960015f80516020613f1683398151915255565b50565b6001600160a01b038216610e4557604051633250574960e11b81525f6004820152602401610997565b5f610e518383336126c5565b9050836001600160a01b0316816001600160a01b031614610e9f576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401610997565b50505050565b5f9081525f80516020613ef6833981519152602052604090206001015490565b610ece82610ea5565b610ed781611fea565b610e9f8383612083565b5f5f80516020613e96833981519152610ef984611431565b8310610f2a5760405163295f44f760e21b81526001600160a01b038516600482015260248101849052604401610997565b6001600160a01b0384165f908152602091825260408082208583529092522054905092915050565b610f5a612124565b5f6040518060600160405280848152602001610f7585611e8d565b8152602001836001600160a01b03168152509050610f928161215b565b50610add60015f80516020613f1683398151915255565b5f620f4240610fb88385613aec565b610fc29190613b03565b9392505050565b6001600160a01b0381163314610ff25760405163334bd91960e11b815260040160405180910390fd5b610ffc82826127c3565b505050565b610ffc83838360405180602001604052805f81525061194d565b600a818154811061102a575f80fd5b5f918252602090912001546001600160401b038082169250600160401b82041690600160801b90046001600160601b031683565b6060805f61106b84611431565b9050805f036110b757604080515f808252602082018181528284019093529091906110ac565b61109961333c565b8152602001906001900390816110915790505b509250925050915091565b806001600160401b038111156110cf576110cf61350b565b6040519080825280602002602001820160405280156110f8578160200160208202803683370190505b509250806001600160401b038111156111135761111361350b565b60405190808252806020026020018201604052801561114c57816020015b61113961333c565b8152602001906001900390816111315790505b5091505f5b8181101561128d575f6111648683610ee1565b90508085838151811061117957611179613ac4565b6020908102919091018101919091525f8281526003808352604091829020825160c081018452815481526001820154948101949094526002810154928401929092528101546060830152600481015460ff161515608083015260058101805460a0840191906111e790613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461121390613a8c565b801561125e5780601f106112355761010080835404028352916020019161125e565b820191905f5260205f20905b81548152906001019060200180831161124157829003601f168201915b50505050508152505084838151811061127957611279613ac4565b602090810291909101015250600101611151565b5050915091565b5f5f80516020613e968339815191526112b85f80516020613ed68339815191525490565b83106112e05760405163295f44f760e21b81525f600482015260248101849052604401610997565b8060020183815481106112f5576112f5613ac4565b905f5260205f200154915050919050565b61130e61333c565b5f82815260036020818152604092839020835160c08101855281548152600182015492810192909252600281015493820193909352908201546060820152600482015460ff161515608082015260058201805491929160a08401919061137390613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461139f90613a8c565b80156113ea5780601f106113c1576101008083540402835291602001916113ea565b820191905f5260205f20905b8154815290600101906020018083116113cd57829003601f168201915b50505050508152505090506114028160600151151590565b61142257604051630169130760e01b815260048101839052602401610997565b919050565b5f61089482611ff4565b5f5f80516020613eb68339815191526001600160a01b038316611469576040516322718ad960e21b81525f6004820152602401610997565b6001600160a01b039092165f908152600390920160205250604090205490565b5f8060015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114db573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114ff9190613b22565b905080600854111561151f57806008546115199190613b39565b91505090565b5f91505090565b5f9182525f80516020613ef6833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b600360208190525f9182526040909120805460018201546002830154938301546004840154600585018054949693959394929360ff9092169261159e90613a8c565b80601f01602080910402602001604051908101604052809291908181526020018280546115ca90613a8c565b80156116155780601f106115ec57610100808354040283529160200191611615565b820191905f5260205f20905b8154815290600101906020018083116115f857829003601f168201915b5050505050905086565b7f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930180546060915f80516020613eb6833981519152916108b990613a8c565b5f805f9054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116ad573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116d19190613b4c565b6116dc90600a613c4c565b610fb88385613aec565b5f6108948260405180602001604052805f8152505b5f611704612124565b825f0361172457604051635e85ae7360e01b815260040160405180910390fd5b5f5461173b906001600160a01b031633308661283c565b6001546040805163540385a360e01b815290515f926001600160a01b03169163540385a39160048083019260209291908290030181865afa158015611782573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117a69190613b22565b600280549192505f6117b783613c5a565b909155506040805160c08101825286815260058054602080840191825283850187815242606086019081525f6080870181815260a088018d81528a83526003958690529890912087518155945160018601559151600285015551918301919091555160048201805460ff1916911515919091179055925193955090928392918201906118439082613cb6565b50905050611851858361165d565b60085f8282546118619190613d71565b9091555061187190503384612896565b336001600160a01b0316837f470a96c327e4382aa8e66bc975ce38a8e3eacb9c8e4d87d34e8dc4a853539de6836040516118ab91906138c8565b60405180910390a3505061089460015f80516020613f1683398151915255565b610add3383836128f7565b5f80516020613f368339815191526118ed81611fea565b620f4240821115611911576040516314afa02960e21b815260040160405180910390fd5b60058290556040518281527fe94edcc114da5287ecb30ee8aacf12e6c16b11b8583d7f9e477ab1616e33d53d9060200160405180910390a15050565b611958848484610e1c565b610e9f33858585856129a6565b604080516060810182525f8082526020820181905291810191909152600a5482106119a55760405162a6e6ff60e61b815260048101839052602401610997565b600a82815481106119b8576119b8613ac4565b5f9182526020918290206040805160608101825291909201546001600160401b038082168352600160401b820416938201939093526001600160601b03600160801b909304929092169082015292915050565b6060611a1682611ff4565b505f611a2c60408051602081019091525f815290565b90505f815111611a4a5760405180602001604052805f815250610fc2565b80611a5484612acd565b604051602001611a65929190613d84565b6040516020818303038152906040529392505050565b611a8482610ea5565b611a8d81611fea565b610e9f83836127c3565b5f8060015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ae9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b0d9190613b22565b905060085481111561151f576008546115199082613b39565b5f5b81811015610ffc575f838383818110611b4357611b43613ac4565b905060600201803603810190611b5991906136e4565b9050611b6481610df2565b50600101611b28565b5f7f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead95611b9881611fea565b6001546040805163540385a360e01b815290515f926001600160a01b03169163540385a39160048083019260209291908290030181865afa158015611bdf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c039190613b22565b90505f6040518060600160405280611c1c600954612b5c565b6001600160401b03168152602001611c3387612b5c565b6001600160401b03168152602001611c4a84612b95565b6001600160601b03908116909152600a80546001810182555f9190915282517fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a88201805460208601516040870151909516600160801b026bffffffffffffffffffffffff60801b196001600160401b03968716600160401b026fffffffffffffffffffffffffffffffff199093169690941695909517179190911692909217909155600254909550909150851115611d23576002546040516326af3c0960e21b8152610997918791600401918252602082015260400190565b6009548511611d535760095460405163b6c78a0360e01b8152610997918791600401918252602082015260400190565b600954604080518781526020810192909252810183905284907fec80db79f7dde8aa35235328c05a056f900bc38a0eb09fe40868458bef7963a09060600160405180910390a250505060099190915590565b6001600160a01b039182165f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab00793056020908152604080832093909416825291909152205460ff1690565b806001600160a01b038116611e195760405163d92e233d60e01b815260040160405180910390fd5b5f80516020613f36833981519152611e3081611fea565b6006546040516001600160a01b038086169216907fa92ff4390fe6943f0b30e8fe715dde86f85ab79b2b2c640a10fc094cc4036cc8905f90a35050600680546001600160a01b0319166001600160a01b0392909216919091179055565b600a545f90808203611eb557604051635f5010c560e11b815260048101849052602401610997565b5f80611ec2600184613b39565b90505b808211611faa575f6002611ed98385613d71565b611ee39190613b03565b90505f600a8281548110611ef957611ef9613ac4565b5f9182526020918290206040805160608101825292909101546001600160401b03808216808552600160401b830490911694840194909452600160801b90046001600160601b03169082015291508710801590611f62575080602001516001600160401b031687105b15611f71575095945050505050565b80516001600160401b0316871015611f9557611f8e600183613b39565b9250611fa3565b611fa0826001613d71565b93505b5050611ec5565b604051635f5010c560e11b815260048101869052602401610997565b5f6001600160e01b03198216637965db0b60e01b1480610894575061089482612bc8565b610e198133612bec565b5f80611fff83612c25565b90506001600160a01b03811661089457604051637e27328960e01b815260048101849052602401610997565b5f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930460205260409020546001600160a01b031690565b610ffc8383836001612c5e565b612079612d71565b610add8282612dbc565b5f5f80516020613ef683398151915261209c8484611526565b61211b575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556120d13390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a46001915050610894565b5f915050610894565b5f80516020613f1683398151915280546001190161215557604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b8051602082015160408301513361217184612c25565b6001600160a01b03161415801561219c5750835133906121909061202b565b6001600160a01b031614155b156121c6578351604051636a931be560e11b81526004810191909152336024820152604401610997565b600a5482106121ea5760405162a6e6ff60e61b815260048101839052602401610997565b5f600a83815481106121fe576121fe613ac4565b5f9182526020918290206040805160608101825292909101546001600160401b03808216808552600160401b830490911694840194909452600160801b90046001600160601b0316908201529150841080612266575080602001516001600160401b03168410155b156122ac578051602082015160405163d0fca86360e01b815260048101879052602481018690526001600160401b03928316604482015291166064820152608401610997565b6040808201515f86815260036020818152848320855160c08101875281548152600182015492810192909252600281015495820195909552908401546060820152600484015460ff16151560808201526005840180546001600160601b0390941694929391929160a08401919061232290613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461234e90613a8c565b80156123995780601f1061237057610100808354040283529160200191612399565b820191905f5260205f20905b81548152906001019060200180831161237c57829003601f168201915b50505050508152505090506123b18160600151151590565b6123d157604051630169130760e01b815260048101879052602401610997565b8060800151156123f7576040516331d5519160e01b815260048101879052602401610997565b612402866009541190565b61242257604051635f5010c560e11b815260048101879052602401610997565b5f86815260036020526040808220600401805460ff19166001179055820151831161244d5782612453565b81604001515b90505f612463835f01518361165d565b90508060085f8282546124769190613b39565b90915550612485905088612dec565b5f548351604051630852cd8d60e31b81526001600160a01b03909216916342966c68916124b89160040190815260200190565b5f604051808303815f87803b1580156124cf575f80fd5b505af11580156124e1573d5f803e3d5ffd5b505050505f6124f4828560200151610fa9565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612547573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061256b9190613b22565b9050828110156125985760405163cf47918160e01b81526004810182905260248101849052604401610997565b6001546001600160a01b031663d2edb68f896125b48587613b39565b8860a001516040518463ffffffff1660e01b81526004016125d793929190613db2565b5f604051808303815f87803b1580156125ee575f80fd5b505af1158015612600573d5f803e3d5ffd5b505050505f8211156126765760015460065460a087015160405163d2edb68f60e01b81526001600160a01b039384169363d2edb68f9361264893911691879190600401613db2565b5f604051808303815f87803b15801561265f575f80fd5b505af1158015612671573d5f803e3d5ffd5b505050505b897f043063fdf65eda59c2092782b3f6984485c435525b12b85fd117d2bd7e2d9a5c338a888d888a6040516126b096959493929190613de1565b60405180910390a25050505050505050505050565b5f806126d2858585612e24565b90506001600160a01b03811661275957612754845f80516020613ed683398151915280545f8381527f645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0360205260408120829055600182018355919091527fa42f15e5d656f8155fd7419d740a6073999f19cd6e061449ce4a257150545bf20155565b61277c565b846001600160a01b0316816001600160a01b03161461277c5761277c8185612f26565b6001600160a01b0385166127985761279384612fbd565b6127bb565b846001600160a01b0316816001600160a01b0316146127bb576127bb858561308a565b949350505050565b5f5f80516020613ef68339815191526127dc8484611526565b1561211b575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a46001915050610894565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610e9f9085906130e2565b6001600160a01b0382166128bf57604051633250574960e11b81525f6004820152602401610997565b5f6128cb83835f6126c5565b90506001600160a01b03811615610ffc576040516339e3563760e11b81525f6004820152602401610997565b5f80516020613eb68339815191526001600160a01b03831661293757604051630b61174360e31b81526001600160a01b0384166004820152602401610997565b6001600160a01b038481165f818152600584016020908152604080832094881680845294825291829020805460ff191687151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a350505050565b6001600160a01b0383163b15610acb57604051630a85bd0160e11b81526001600160a01b0384169063150b7a02906129e8908890889087908790600401613e2a565b6020604051808303815f875af1925050508015612a22575060408051601f3d908101601f19168201909252612a1f91810190613e66565b60015b612a89573d808015612a4f576040519150601f19603f3d011682016040523d82523d5f602084013e612a54565b606091505b5080515f03612a8157604051633250574960e11b81526001600160a01b0385166004820152602401610997565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b14612ac557604051633250574960e11b81526001600160a01b0385166004820152602401610997565b505050505050565b60605f612ad98361314e565b60010190505f816001600160401b03811115612af757612af761350b565b6040519080825280601f01601f191660200182016040528015612b21576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084612b2b57509392505050565b5f6001600160401b03821115612b9157604080516306dfcc6560e41b8152600481019190915260248101839052604401610997565b5090565b5f6001600160601b03821115612b91576040516306dfcc6560e41b81526060600482015260248101839052604401610997565b5f6001600160e01b0319821663780e9d6360e01b1480610894575061089482613225565b612bf68282611526565b610add5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610997565b5f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930260205260409020546001600160a01b031690565b5f80516020613eb68339815191528180612c8057506001600160a01b03831615155b15612d41575f612c8f85611ff4565b90506001600160a01b03841615801590612cbb5750836001600160a01b0316816001600160a01b031614155b8015612cce5750612ccc8185611da5565b155b15612cf75760405163a9fbf51f60e01b81526001600160a01b0385166004820152602401610997565b8215612d3f5784866001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b5f93845260040160205250506040902080546001600160a01b0319166001600160a01b0392909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16612dba57604051631afcd79f60e31b815260040160405180910390fd5b565b612dc4612d71565b5f80516020613eb683398151915280612ddd8482613cb6565b5060018101610e9f8382613cb6565b5f612df85f835f6126c5565b90506001600160a01b038116610add57604051637e27328960e01b815260048101839052602401610997565b5f5f80516020613eb683398151915281612e3d85612c25565b90506001600160a01b03841615612e5957612e59818587613274565b6001600160a01b03811615612e9557612e745f865f80612c5e565b6001600160a01b0381165f908152600383016020526040902080545f190190555b6001600160a01b03861615612ec5576001600160a01b0386165f9081526003830160205260409020805460010190555b5f85815260028301602052604080822080546001600160a01b0319166001600160a01b038a811691821790925591518893918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a495945050505050565b5f80516020613e968339815191525f612f3e84611431565b5f8481526001840160209081526040808320546001600160a01b03891684529186905290912091925090818314612f96575f838152602082815260408083205485845281842081905583526001870190915290208290555b5f948552600190930160209081526040808620869055928552929092528220919091555050565b5f80516020613ed6833981519152545f80516020613e96833981519152905f90612fe990600190613b39565b5f84815260038401602052604081205460028501805493945090928490811061301457613014613ac4565b905f5260205f20015490508084600201838154811061303557613035613ac4565b5f9182526020808320909101929092558281526003860190915260408082208490558682528120556002840180548061307057613070613e81565b600190038181905f5260205f20015f905590555050505050565b5f80516020613e968339815191525f60016130a485611431565b6130ae9190613b39565b6001600160a01b039094165f9081526020838152604080832087845282528083208690559482526001909301909252502055565b5f8060205f8451602086015f885af180613101576040513d5f823e3d81fd5b50505f513d91508115613118578060011415613125565b6001600160a01b0384163b155b15610e9f57604051635274afe760e01b81526001600160a01b0385166004820152602401610997565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061318c5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106131b8576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106131d657662386f26fc10000830492506010015b6305f5e10083106131ee576305f5e100830492506008015b612710831061320257612710830492506004015b60648310613214576064830492506002015b600a83106108945760010192915050565b5f6001600160e01b031982166380ac58cd60e01b148061325557506001600160e01b03198216635b5e139f60e01b145b8061089457506301ffc9a760e01b6001600160e01b0319831614610894565b61327f8383836132d8565b610ffc576001600160a01b0383166132ad57604051637e27328960e01b815260048101829052602401610997565b60405163177e802f60e01b81526001600160a01b038316600482015260248101829052604401610997565b5f6001600160a01b038316158015906127bb5750826001600160a01b0316846001600160a01b0316148061331157506133118484611da5565b806127bb5750826001600160a01b031661332a8361202b565b6001600160a01b031614949350505050565b6040518060c001604052805f81526020015f81526020015f81526020015f81526020015f15158152602001606081525090565b6001600160e01b031981168114610e19575f80fd5b5f60208284031215613394575f80fd5b8135610fc28161336f565b5f5b838110156133b95781810151838201526020016133a1565b50505f910152565b5f81518084526133d881602086016020860161339f565b601f01601f19169290920160200192915050565b602081525f610fc260208301846133c1565b5f6020828403121561340e575f80fd5b5035919050565b5f8083601f840112613425575f80fd5b5081356001600160401b0381111561343b575f80fd5b6020830191508360208260051b8501011115613455575f80fd5b9250929050565b5f805f806040858703121561346f575f80fd5b84356001600160401b0380821115613485575f80fd5b61349188838901613415565b909650945060208701359150808211156134a9575f80fd5b506134b687828801613415565b95989497509550505050565b6001600160a01b0381168114610e19575f80fd5b8035611422816134c2565b5f80604083850312156134f2575f80fd5b82356134fd816134c2565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b60405161014081016001600160401b03811182821017156135425761354261350b565b60405290565b5f82601f830112613557575f80fd5b81356001600160401b03808211156135715761357161350b565b604051601f8301601f19908116603f011681019082821181831017156135995761359961350b565b816040528381528660208588010111156135b1575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f602082840312156135e0575f80fd5b81356001600160401b03808211156135f6575f80fd5b90830190610140828603121561360a575f80fd5b61361261351f565b823582811115613620575f80fd5b61362c87828601613548565b825250602083013582811115613640575f80fd5b61364c87828601613548565b60208301525061365e604084016134d6565b604082015261366f606084016134d6565b6060820152613680608084016134d6565b608082015261369160a084016134d6565b60a08201526136a260c084016134d6565b60c082015260e083013560e082015261010091506136c18284016134d6565b8282015261012091506136d58284016134d6565b91810191909152949350505050565b5f606082840312156136f4575f80fd5b604051606081018181106001600160401b03821117156137165761371661350b565b806040525082358152602083013560208201526040830135613737816134c2565b60408201529392505050565b5f805f60608486031215613755575f80fd5b8335613760816134c2565b92506020840135613770816134c2565b929592945050506040919091013590565b5f8060408385031215613792575f80fd5b8235915060208301356137a4816134c2565b809150509250929050565b5f80604083850312156137c0575f80fd5b50508035926020909101359150565b5f602082840312156137df575f80fd5b8135610fc2816134c2565b805182526020810151602083015260408101516040830152606081015160608301526080810151151560808301525f60a082015160c060a08501526127bb60c08501826133c1565b604080825283519082018190525f906020906060840190828701845b8281101561386a5781518452928401929084019060010161384e565b50505083810382850152845180825282820190600581901b830184018785015f5b838110156138b957601f198684030185526138a78383516137ea565b9487019492509086019060010161388b565b50909998505050505050505050565b602081525f610fc260208301846137ea565b868152856020820152846040820152836060820152821515608082015260c060a08201525f61390c60c08301846133c1565b98975050505050505050565b5f8060408385031215613929575f80fd5b8235915060208301356001600160401b03811115613945575f80fd5b61395185828601613548565b9150509250929050565b5f806040838503121561396c575f80fd5b8235613977816134c2565b9150602083013580151581146137a4575f80fd5b5f805f806080858703121561399e575f80fd5b84356139a9816134c2565b935060208501356139b9816134c2565b92506040850135915060608501356001600160401b038111156139da575f80fd5b6139e687828801613548565b91505092959194509250565b5f8060208385031215613a03575f80fd5b82356001600160401b0380821115613a19575f80fd5b818501915085601f830112613a2c575f80fd5b813581811115613a3a575f80fd5b866020606083028501011115613a4e575f80fd5b60209290920196919550909350505050565b5f8060408385031215613a71575f80fd5b8235613a7c816134c2565b915060208301356137a4816134c2565b600181811c90821680613aa057607f821691505b602082108103613abe57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761089457610894613ad8565b5f82613b1d57634e487b7160e01b5f52601260045260245ffd5b500490565b5f60208284031215613b32575f80fd5b5051919050565b8181038181111561089457610894613ad8565b5f60208284031215613b5c575f80fd5b815160ff81168114610fc2575f80fd5b600181815b80851115613ba657815f1904821115613b8c57613b8c613ad8565b80851615613b9957918102915b93841c9390800290613b71565b509250929050565b5f82613bbc57506001610894565b81613bc857505f610894565b8160018114613bde5760028114613be857613c04565b6001915050610894565b60ff841115613bf957613bf9613ad8565b50506001821b610894565b5060208310610133831016604e8410600b8410161715613c27575081810a610894565b613c318383613b6c565b805f1904821115613c4457613c44613ad8565b029392505050565b5f610fc260ff841683613bae565b5f60018201613c6b57613c6b613ad8565b5060010190565b601f821115610ffc57805f5260205f20601f840160051c81016020851015613c975750805b601f840160051c820191505b81811015610acb575f8155600101613ca3565b81516001600160401b03811115613ccf57613ccf61350b565b613ce381613cdd8454613a8c565b84613c72565b602080601f831160018114613d16575f8415613cff5750858301515b5f19600386901b1c1916600185901b178555612ac5565b5f85815260208120601f198616915b82811015613d4457888601518255948401946001909101908401613d25565b5085821015613d6157878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561089457610894613ad8565b5f8351613d9581846020880161339f565b835190830190613da981836020880161339f565b01949350505050565b60018060a01b0384168152826020820152606060408201525f613dd860608301846133c1565b95945050505050565b6001600160a01b0387811682528616602082015260c0604082018190525f90613e0c908301876137ea565b606083019590955250608081019290925260a0909101529392505050565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f90613e5c908301846133c1565b9695505050505050565b5f60208284031215613e76575f80fd5b8151610fc28161336f565b634e487b7160e01b5f52603160045260245ffdfe645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0080bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab0079300645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0202dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268009b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00947478cb7f25f91a774cb9fce49ddd1952e530220912f89fefb6a8e0d8f04381a26469706673582212205241c4d1fe20f348b8047036f3a441befb91619aa43d7d7d612ce62bbb43aa4964736f6c63430008180033", + "nonce": "0x1c2", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x0fc0d63a39064747a2a4ccb7b6b1abe9420f7d74177cb732d4f13b11ea799690", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x141aab320857145fb42240c979b800f48ce5b678", + "function": null, + "arguments": [ + "0x28732d8061D35B77dC2997926e0449dAdc3Ef3DD", + "0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913", + "0x" + ], + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610300000000000000000000000028732d8061d35b77dc2997926e0449dadc3ef3dd000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b91300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x1c3", + "chainId": "0x4268" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0x26425968beb4bd9c09b02c433996cd05b1fd4f07", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + } + ], + "isFixedGasLimit": false + }, + { + "hash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionType": "CALL", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "gas": "0x3973e", + "value": "0x0", + "input": "0xd26b3e26000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913000000000000000000000000141aab320857145fb42240c979b800f48ce5b678000000000000000000000000d9029669bc74878bcb5be58c259ed0a277c5c16e", + "nonce": "0x1c4", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionType": "CALL", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x141aab320857145fb42240c979b800f48ce5b678", + "function": null, + "arguments": null, + "transaction": { + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x141aab320857145fb42240c979b800f48ce5b678", + "gas": "0x61a4e", + "value": "0x0", + "input": "0x0cc2250f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000180000000000000000000000000d9029669bc74878bcb5be58c259ed0a277c5c16e0000000000000000000000003a2dd2f0f5a20768110a52fc4f091ab9d8631b58000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b9130000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d390000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d3900000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b9130000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d390000000000000000000000000000000000000000000000000000000000000018796e455448205769746864726177616c204d616e6167657200000000000000000000000000000000000000000000000000000000000000000000000000000007796e455448574d00000000000000000000000000000000000000000000000000", + "nonce": "0x1c5", + "chainId": "0x4268" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x51b82a", + "logs": [ + { + "address": "0xc9cf6740282617f3b392f900de0449e687ce05e3", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x01ed70f32a7f0fd06a98a9780c661b2be964324c6707d033786764a72df8876e", + "transactionIndex": "0xe", + "logIndex": "0x14", + "removed": false + } + ], + "logsBloom": "0x00000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000004000000000400000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x01ed70f32a7f0fd06a98a9780c661b2be964324c6707d033786764a72df8876e", + "transactionIndex": "0xe", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x3e39ec", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0xc9cf6740282617f3b392f900de0449e687ce05e3" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x7514da", + "logs": [ + { + "address": "0xf07861349ed0cb4603590b47d2269768ed6e2821", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x0ca23b8da2816cb17b11c5b34ea668c645f45dd0246f6e1eae59a5b87819a19e", + "transactionIndex": "0xf", + "logIndex": "0x15", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000800000000000000000000000080000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x0ca23b8da2816cb17b11c5b34ea668c645f45dd0246f6e1eae59a5b87819a19e", + "transactionIndex": "0xf", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x235cb0", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0xf07861349ed0cb4603590b47d2269768ed6e2821" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x92ba9b", + "logs": [ + { + "address": "0x090d67d3c97712f6c17a037515cbb8502561ee57", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x3479f796160c2ee6003d1433b139439de226ff74a9556dd835a3fce5f519b340", + "transactionIndex": "0x10", + "logIndex": "0x16", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000004000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x3479f796160c2ee6003d1433b139439de226ff74a9556dd835a3fce5f519b340", + "transactionIndex": "0x10", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x1da5c1", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x090d67d3c97712f6c17a037515cbb8502561ee57" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xa024c2", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e4d1c3b95813e51405e6ba50a914aaa57f505916bb93d6f75df78b2a6023fe2", + "transactionIndex": "0x11", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0xd6a27", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x7ebe6eac9ad9b5e4d637c8f05191b273b009461a" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xaa11a6", + "logs": [ + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x0000000000000000000000007ebe6eac9ad9b5e4d637c8f05191b273b009461a" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfbf7b311265081a42e6e5e727d2cb34c5016583d1c733acbb3ad7fdf5ef5929a", + "transactionIndex": "0x12", + "logIndex": "0x17", + "removed": false + }, + { + "address": "0xd4d8e80d1c959cdc4601cc6a0cc1ec18abeb1888", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfbf7b311265081a42e6e5e727d2cb34c5016583d1c733acbb3ad7fdf5ef5929a", + "transactionIndex": "0x12", + "logIndex": "0x18", + "removed": false + }, + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d4d8e80d1c959cdc4601cc6a0cc1ec18abeb1888", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfbf7b311265081a42e6e5e727d2cb34c5016583d1c733acbb3ad7fdf5ef5929a", + "transactionIndex": "0x12", + "logIndex": "0x19", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000404000000000000000800000000000000000000000000004000020000000000000000000400000000000000004000000000000000000000000000000000002000081000000000000000000000000000000000000020000000000000000000800000200800000000000000000000000400040000000000000000000000000000000000000000000000000000000800000000000000000000000200000000010000400000000000000000000000000000000000020000000000000000000000000000000000400000000000000000020000000000000000400000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xfbf7b311265081a42e6e5e727d2cb34c5016583d1c733acbb3ad7fdf5ef5929a", + "transactionIndex": "0x12", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xe0f6ef", + "logs": [ + { + "address": "0x28732d8061d35b77dc2997926e0449dadc3ef3dd", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x1c9967ffe3fed1052a8922862e82456bf6511c1bab6c97fd9940aaec95993442", + "transactionIndex": "0x13", + "logIndex": "0x1a", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x1c9967ffe3fed1052a8922862e82456bf6511c1bab6c97fd9940aaec95993442", + "transactionIndex": "0x13", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x36e549", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x28732d8061d35b77dc2997926e0449dadc3ef3dd" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xeae3d3", + "logs": [ + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000028732d8061d35b77dc2997926e0449dadc3ef3dd" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x0fc0d63a39064747a2a4ccb7b6b1abe9420f7d74177cb732d4f13b11ea799690", + "transactionIndex": "0x14", + "logIndex": "0x1b", + "removed": false + }, + { + "address": "0x26425968beb4bd9c09b02c433996cd05b1fd4f07", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x0fc0d63a39064747a2a4ccb7b6b1abe9420f7d74177cb732d4f13b11ea799690", + "transactionIndex": "0x14", + "logIndex": "0x1c", + "removed": false + }, + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026425968beb4bd9c09b02c433996cd05b1fd4f07", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0x0fc0d63a39064747a2a4ccb7b6b1abe9420f7d74177cb732d4f13b11ea799690", + "transactionIndex": "0x14", + "logIndex": "0x1d", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000400000000000000000900000000000000000000000000004000000000000000000000000000000000008000400000000000000000000000000000000000002000881000000000000000000000000000000000000020000000000000004000800000000800000000000000000000000400000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000820000000000000000000200000000000000400000000001000000020000000000000400400000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x0fc0d63a39064747a2a4ccb7b6b1abe9420f7d74177cb732d4f13b11ea799690", + "transactionIndex": "0x14", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": null, + "contractAddress": "0x141aab320857145fb42240c979b800f48ce5b678" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xed7d57", + "logs": [ + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionIndex": "0x15", + "logIndex": "0x1e", + "removed": false + }, + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionIndex": "0x15", + "logIndex": "0x1f", + "removed": false + }, + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionIndex": "0x15", + "logIndex": "0x20", + "removed": false + }, + { + "address": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionIndex": "0x15", + "logIndex": "0x21", + "removed": false + } + ], + "logsBloom": "0x00000004000000000000000000000000000000000800000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000010000000000000020000000000000000000900000200000000000000000000000000000000000000000000000800000000008000000000000080000000000001000000000000000000000000200000000000000400000000000000001000000000000000000000000000000000000000000020000000000004000100002000000020000000000000000400000000000000000000000400000000000000000000000000", + "type": "0x2", + "transactionHash": "0xfa59a46dc21a8e8e35c180e6394bfccbc8b41c6ca2aaf9dca98a96309b477f50", + "transactionIndex": "0x15", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x29984", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x3a2dd2f0f5a20768110a52fc4f091ab9d8631b58", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xf1e86c", + "logs": [ + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000743b91cdb1c694d4f51bcda3a4a59dcc0d02b913", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "logIndex": "0x22", + "removed": false + }, + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x947478cb7f25f91a774cb9fce49ddd1952e530220912f89fefb6a8e0d8f04381", + "0x0000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d39", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "logIndex": "0x23", + "removed": false + }, + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e", + "0x0000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d39", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "logIndex": "0x24", + "removed": false + }, + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d", + "0x8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead95", + "0x0000000000000000000000009dd8f69b62ddfd990241530f47dced0dad7f7d39", + "0x000000000000000000000000d7d4a7f14265b0a69ae482b85b0a8496c3f99fb9" + ], + "data": "0x", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "logIndex": "0x25", + "removed": false + }, + { + "address": "0x141aab320857145fb42240c979b800f48ce5b678", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "logIndex": "0x26", + "removed": false + } + ], + "logsBloom": "0x00000084000000000000000000000400000000000800000000000000000000000000000000000004000000000000000000000000000000000000000400000000000040000000000000000000000000000080000000000000000000000000800000000004020000000000000004000900000000000000000000000000000000000000000000000000000800800000010000000020000080000000000000000000020800000000400000000000000000000000000000000000001000000000000000000000000000000000000000000000008000000004000100000000000020000000000000400400000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xfa882ae7d798f4a42ad3cc5e0643395507f550eee6544b255026ea14ec3596cc", + "transactionIndex": "0x16", + "blockHash": "0xb55b957a63d6bbfbd6a44ba32bd251a150477131f121887915700350206668d0", + "blockNumber": "0x2538c0", + "gasUsed": "0x46b15", + "effectiveGasPrice": "0x2fd62cb", + "from": "0xd7d4a7f14265b0a69ae482b85b0a8496c3f99fb9", + "to": "0x141aab320857145fb42240c979b800f48ce5b678", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1727702077, + "chain": 17000, + "commit": "55f1dd7" +} \ No newline at end of file diff --git a/deployments/YnLSDe-1.json b/deployments/YnLSDe-1.json index aac6385a9..b1b7c43b0 100644 --- a/deployments/YnLSDe-1.json +++ b/deployments/YnLSDe-1.json @@ -11,21 +11,21 @@ "UNPAUSE_ADMIN": "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", "YNDev": "0xa08F39d30dc865CC11a49b6e5cBd27630D6141C3", "YnSecurityCouncil": "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", - "implementation-YnLSDe": "0x3688bAce1C507b368b5061F97cB48037f0Df6b2f", + "implementation-ynLSDe": "0x3688bAce1C507b368b5061F97cB48037f0Df6b2f", "implementation-assetRegistry": "0x84dA5aB890B0a3314deEd0BeBd2463309a755EAc", "implementation-eigenStrategyManager": "0xE2343a850394ee1b1e33b16Da5bD73B076D6Fa81", "implementation-rateProvider": "0xAEDC83F607126b4D2437F79285e2C635685BBA01", "implementation-tokenStakingNodesManager": "0x17e77F964538fed3ADEc3E8dBB6147dA258206Da", - "implementation-ynEigenDepositAdapter": "0x5F8c55A5A33d63CAa231c668a62f69AA862020E9", + "implementation-ynEigenDepositAdapter": "0xA9CD5a5199177b704287e379e85af95F0f061Bf2", "implementation-ynEigenViewer": "0x95046553147036dA98Aecc02B2526FCBc36F4306", - "proxy-YnLSDe": "0x35Ec69A77B79c255e5d47D5A3BdbEFEfE342630c", + "proxy-ynLSDe": "0x35Ec69A77B79c255e5d47D5A3BdbEFEfE342630c", "proxy-assetRegistry": "0x323C933df2523D5b0C756210446eeE0fB84270fd", "proxy-eigenStrategyManager": "0x92D904019A92B0Cafce3492Abb95577C285A68fC", "proxy-rateProvider": "0xb658Cf6F4C232Be5c6035f2b42b96393089F20D9", "proxy-tokenStakingNodesManager": "0x6B566CB6cDdf7d140C59F84594756a151030a0C3", - "proxy-ynEigenDepositAdapter": "0xA9CD5a5199177b704287e379e85af95F0f061Bf2", + "proxy-ynEigenDepositAdapter": "0x9e72155d301a6555dc565315be72D295c76753c0", "proxy-ynEigenViewer": "0x9B933D84Fac0782F3B275D76B64a0DBf6FBEf28F", - "proxyAdmin-YnLSDe": "0x1bfF798A6072597c401f7C83A6a1096Ae5F0aFf7", + "proxyAdmin-ynLSDe": "0x1bfF798A6072597c401f7C83A6a1096Ae5F0aFf7", "proxyAdmin-assetRegistry": "0xDf92BE359fB53846cCdaC8FFF1884314533949C7", "proxyAdmin-eigenStrategyManager": "0x69C34FB00Ebc0d7ac09Ab7968D2Eb9a07C2eB301", "proxyAdmin-rateProvider": "0x16c95825faD0CE0dD56698bc8Ab49E7B00Fa29d9", diff --git a/deployments/YnLSDe-17000.json b/deployments/YnLSDe-17000.json index 47f1d1fa7..3fcdb41e6 100644 --- a/deployments/YnLSDe-17000.json +++ b/deployments/YnLSDe-17000.json @@ -11,24 +11,24 @@ "UNPAUSE_ADMIN": "0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913", "YNDev": "0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39", "YnSecurityCouncil": "0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913", - "implementation-YnLSDe": "0xA6cf070663127FAa41a2150BdadAD8D84b876AEb", + "implementation-ynLSDe": "0x92Df7b6001EF4cFe40B6b425bC4a358c05f27624", "implementation-assetRegistry": "0x7385f1dBF3b0Ee3d85c89A38901Aa87333D30946", "implementation-eigenStrategyManager": "0xc599064e66FB509FD49B5083896649a742066758", - "implementation-rateProvider": "0x94009d1296dc2e6370Aa34c94BEDFeD55f96a276", + "implementation-rateProvider": "0x0Ca50907B8B5fDc6b0B1d5F23195209aD0457113", "implementation-tokenStakingNodesManager": "0xAD89Fd40BE78811E279C89FF15DC3B831785192D", "implementation-ynEigenDepositAdapter": "0x7af5cc12cee35dee9ccd0aa4d5e1772eb45b2046", "implementation-ynEigenViewer": "0x85bB85b81Db2f9b446e9100057420AE5DE7232E4", - "proxy-YnLSDe": "0x071bdC8eDcdD66730f45a3D3A6F794FAA37C75ED", + "proxy-ynLSDe": "0x071bdC8eDcdD66730f45a3D3A6F794FAA37C75ED", "proxy-assetRegistry": "0xaD31546AdbfE1EcD7137310508f112039a35b6F7", "proxy-eigenStrategyManager": "0xA0a11A9b84bf87c0323bc183715a22eC7881B7FC", "proxy-rateProvider": "0xd68C29263F6dC2Ff8D9307b3AfAcD6D6fDeFbB3A", "proxy-tokenStakingNodesManager": "0x5c20D1a85C7d9acB503135a498E26Eb55d806552", "proxy-ynEigenDepositAdapter": "0x7d0c1F604571a1c015684e6c15f2DdEc432C5e74", "proxy-ynEigenViewer": "0xECCE549926637E974Ca5739a8827E612682415E7", - "proxyAdmin-YnLSDe": "0x92Df7b6001EF4cFe40B6b425bC4a358c05f27624", + "proxyAdmin-ynLSDe": "0x31456Eef519b7ab236e3638297Ed392390bf304F", "proxyAdmin-assetRegistry": "0x4248392db8Ee31aA579822207d059A28A38c4510", "proxyAdmin-eigenStrategyManager": "0x010c60d663fddDAA076F0cE63f6692f0b5605fE5", - "proxyAdmin-rateProvider": "0x401af3714dc0c0d28f752dbdd70cf423c667246e", + "proxyAdmin-rateProvider": "0xD7E36d7f08ea756bb699B679B49F3095Cb6F7AB8", "proxyAdmin-tokenStakingNodesManager": "0x18ED5129bCEfA996B4cade4e614c8941De3126d2", "proxyAdmin-ynEigenDepositAdapter": "0x9E9ce6D0fD72c7A31Eb7D99d8eCEA4b35a4FD088", "proxyAdmin-ynEigenViewer": "0x4879936e5822b936A9981f8F67844BC77096d433", diff --git a/deployments/ynETH-1.json b/deployments/ynETH-1.json index 897498621..e0724d7f7 100644 --- a/deployments/ynETH-1.json +++ b/deployments/ynETH-1.json @@ -15,17 +15,29 @@ "implementation-consensusLayerReceiver": "0xa98e8652DAF2407839b18f4e215D86D88Eb91582", "implementation-executionLayerReceiver": "0x863Ba8f64b1528De6c8A7239D80D4f2b7CfF159B", "implementation-rewardsDistributor": "0x306EBed66642867B94b29752be37b8f20fEb5F72", - "implementation-stakingNodesManager": "0xaBD3A755E4eEF24f862c268D21Fd5235904cc811", - "implementation-ynETH": "0x14DC3d915107DcA9ed39E29E14fbdfe4358a1346", + "implementation-stakingNodesManager": "0x33Ffb713C73Cdf668b11296Bb9B1C9a0C3100626", + "implementation-ynETH": "0x87E2A51d3B88FC2f5917a7aB793ea595b243710a", + "implementation-ynViewer": "0x0365a6eF790e05EEe386B57326e5Ceaf5B10899e", + "implementation-withdrawalQueueManager": "0x6ac38ab99053a2585B6184D0AD72018Ae22185CD", + "implementation-withdrawalsProcessor": "0x97e619c4573ce55A3Aa8e105E339dce00e3bF3B2", + "implementation-ynETHRedemptionAssetsVault": "0xe1a2A7FAc6729360e6794f2f137621E2Cdb0Fed5", "proxy-consensusLayerReceiver": "0xE439fe4563F7666FCd7405BEC24aE7B0d226536e", "proxy-executionLayerReceiver": "0x1D6b2a11FFEa5F9a8Ed85A02581910b3d695C12b", "proxy-rewardsDistributor": "0x40d5FF3E218f54f4982661a0464a298Cf6652351", "proxy-stakingNodesManager": "0x8C33A1d6d062dB7b51f79702355771d44359cD7d", "proxy-ynETH": "0x09db87A538BD693E9d08544577d5cCfAA6373A48", + "proxy-ynViewer": "0xF0207Ffa0b793E009DF9Df62fEE95B8FC6c93EcF", + "proxy-withdrawalQueueManager": "0x0BC9BC81aD379810B36AD5cC95387112990AA67b", + "proxy-withdrawalsProcessor": "0x6d052CdEd3F64aea51f6051F33b68b42016C5FbA", + "proxy-ynETHRedemptionAssetsVault": "0x5D6e53c42E3B37f82F693937BC508940769c5caf", "proxyAdmin-consensusLayerReceiver": "0xaB5CC95BD4F5C45F637B6D319169Bbbc212CfEE8", "proxyAdmin-executionLayerReceiver": "0x5a3d6625C6d26E3650675bF2b7041cd7d16A63B2", "proxyAdmin-rewardsDistributor": "0x17ecBFce53B364Fd571D159eA2b766bA3114d85E", "proxyAdmin-stakingNodesManager": "0x091929600D3891861615098fc9eA3a0270966398", "proxyAdmin-ynETH": "0x12e9DA7e671342E7A5fDD7197348C8019e72C31F", - "stakingNodeImplementation": "0xF8A37F34a427611C4c54F82bF43230FA041d237B" + "proxyAdmin-ynViewer": "0xcAD4147b889D5A5564575fDA6B2910eeF5eBA03A", + "proxyAdmin-withdrawalQueueManager": "0xd3f08F92D6752B01FC5b706AdbE254B222A1BdaA", + "proxyAdmin-withdrawalsProcessor": "0xb5234121Be2144975a123C4457606e6F4e5E2a6b", + "proxyAdmin-ynETHRedemptionAssetsVault": "0x364b8154Ea1673E360446c3D17480d32C3E71B2c", + "stakingNodeImplementation": "0xA8bD8f089F5Bd0BeE61e23B3A0a4aA1332Fe929d" } \ No newline at end of file diff --git a/deployments/ynETH-17000.json b/deployments/ynETH-17000.json index da706e168..b846dcf08 100644 --- a/deployments/ynETH-17000.json +++ b/deployments/ynETH-17000.json @@ -11,21 +11,32 @@ "STAKING_NODES_OPERATOR": "0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39", "STAKING_NODE_CREATOR": "0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39", "VALIDATOR_MANAGER": "0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39", - "implementation-StakingNodesManager": "0xDDd8f9Ad981444a0C44c611E9438b06FFc2bd354", - "implementation-ynETH": "0x0E60A5db38c545ced3d3480B6768811cc603649A", + "implementation-stakingNodesManager": "0xC9cf6740282617f3B392f900De0449E687Ce05e3", + "implementation-ynETH": "0x090D67d3C97712f6C17a037515CbB8502561EE57", "implementation-executionLayerReceiver": "0x1fbedf3773418f20b9dfeafcd9d263030eb0e42f", "implementation-consensusLayerReceiver": "0xe7acc0533c650ad0cc11f57f81c38fa19634b1d7", - "implementation-StakingNode": "0xe01a7328090c95e89Ec689b005392Cf7e5De90d9", - "implementation-RewardsDistributor": "0xb6ec4d9f71e437c672147c576f1c70ba5da8d159", + "implementation-rewardsDistributor": "0xb6ec4d9f71e437c672147c576f1c70ba5da8d159", + "implementation-withdrawalsProcessor": "0x36a38AA91947DbE6539e19512E6FF26576015Bb2", + "implementation-withdrawalQueueManager": "0x28732d8061D35B77dC2997926e0449dAdc3Ef3DD", + "implementation-ynETHRedemptionAssetsVault": "0x7eBE6EAC9AD9B5e4D637c8f05191b273b009461a", + "implementation-ynViewer": "0xBf1035e71E4a770a08462B20C95dE14763E1D186", "proxy-consensusLayerReceiver": "0x706EED02702fFE9CBefD6A65E63f3C2b59B7eF2d", "proxy-executionLayerReceiver": "0xA5E9E1ceb4cC1854d0e186a9B3E67158b84AD072", "proxy-rewardsDistributor": "0x82915efF62af9FCC0d0735b8681959e069E3f2D8", "proxy-stakingNodesManager": "0xc2387EBb4Ea66627E3543a771e260Bd84218d6a1", "proxy-ynETH": "0xd9029669BC74878BCB5BE58c259ed0A277C5c16E", + "proxy-ynViewer": "0x61810a90128ee5c5f5a3730f0449da9e9480f888", + "proxy-withdrawalsProcessor": "0x48E3FdCE3E2d5A3Fa34bdEd9eb9dEeBB48217ba3", + "proxy-withdrawalQueueManager": "0x141aAb320857145fB42240C979b800f48CE5B678", + "proxy-ynETHRedemptionAssetsVault": "0x3a2DD2f0f5A20768110a52fC4f091AB9d8631b58", "proxyAdmin-ynETH": "0x4ed4d42a2001d240e20304f58c626a63a3eb7ff4", "proxyAdmin-stakingNodesManager": "0xb0165e7584ebf6d76f0dd95e58dea6bdd759f238", "proxyAdmin-rewardsDistributor": "0xc0300eefe38a393681b6eca7912985a165d37c0e", "proxyAdmin-executionLayerReceiver": "0x319f70591f72c5d69823dfc673acdfefd6aee5a1", "proxyAdmin-consensusLayerReceiver": "0x6234fd395c1f090334606efbf3cbf3be46fcf177", - "stakingNodeImplementation": "0xe01a7328090c95e89Ec689b005392Cf7e5De90d9" + "proxyAdmin-withdrawalsProcessor": "0x22D9bc7555875af63a51c20026f02E752781A8c0", + "proxyAdmin-withdrawalQueueManager": "0x26425968beb4bD9c09b02C433996Cd05B1Fd4f07", + "proxyAdmin-ynETHRedemptionAssetsVault": "0xd4d8E80d1c959CdC4601Cc6A0cc1EC18aBeB1888", + "proxyAdmin-ynViewer": "0x3ffa0c3fba4adfe2b6e4d7e2f8e6e6324be5305b", + "stakingNodeImplementation": "0xf07861349Ed0cB4603590B47D2269768Ed6E2821" } \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNode-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNode-1.json new file mode 100644 index 000000000..973251594 --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNode-1.json @@ -0,0 +1,60 @@ +{ + "transactions": [ + { + "hash": "0xa7b95e51c3329685699a8f6e7596848a039c514212da7ddf979c9459f598469e", + "transactionType": "CREATE", + "contractName": "StakingNode", + "contractAddress": "0xa8bd8f089f5bd0bee61e23b3a0a4aa1332fe929d", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x2df87e", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61286d80620000e35f395ff3fe60806040526004361061014a575f3560e01c806379cb2d2f116100b3578063b3c650151161006d578063b3c65015146103c5578063bfe8172d146103f1578063d609099314610405578063ef7953a21461041a578063f55b30401461042f578063f99d9e551461044e575f80fd5b806379cb2d2f1461030d578063852aa33d1461032057806388676cad1461034c5780639104c3191461036b57806392ab89bb14610392578063a3aae136146103a6575f80fd5b8063443dc42811610104578063443dc4281461026a5780635b4e128c146102885780635c60da1b146102a75780635d21e3da146102bb5780636e947298146102da57806377b34927146102ee575f80fd5b80630b10b201146101b8578063139d7fed146101e957806319259db01461020c5780631f1fb913146102215780632b72a3ca146102355780633f65cf1914610249575f80fd5b366101b4576001546001600160a01b0316331461017a576040516341e3874160e11b815260040160405180910390fd5b604080513381523460208201527fbfe611b001dfcd411432f7bf0d79b82b4b2ee81511edac123a3403c357fb972a910160405180910390a1005b5f80fd5b3480156101c3575f80fd5b506101cc610461565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101f4575f80fd5b506101fe60025481565b6040519081526020016101e0565b348015610217575f80fd5b506101fe60065481565b34801561022c575f80fd5b506006546101fe565b348015610240575f80fd5b506004546101fe565b348015610254575f80fd5b50610268610263366004611b52565b61064f565b005b348015610275575f80fd5b505f546101cc906001600160a01b031681565b348015610293575f80fd5b506102686102a2366004611c1f565b6108cf565b3480156102b2575f80fd5b506101cc6109b8565b3480156102c6575f80fd5b506102686102d5366004611ceb565b610a5a565b3480156102e5575f80fd5b506101fe610bfd565b3480156102f9575f80fd5b50610268610308366004611dc5565b610d12565b61026861031b366004611c1f565b610e5c565b34801561032b575f80fd5b5061033f61033a366004611c1f565b610f80565b6040516101e09190611e37565b348015610357575f80fd5b50610268610366366004611e5d565b611279565b348015610376575f80fd5b506101cc73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b34801561039d575f80fd5b5061026861135c565b3480156103b1575f80fd5b506001546101cc906001600160a01b031681565b3480156103d0575f80fd5b506103d9611565565b6040516001600160401b0390911681526020016101e0565b3480156103fc575f80fd5b506005546101fe565b348015610410575f80fd5b506101fe60055481565b348015610425575f80fd5b506101fe60045481565b34801561043a575f80fd5b50610268610449366004611f7e565b611589565b61026861045c366004611c1f565b611930565b5f61046a6119af565b6001546001600160a01b03161561048d57506001546001600160a01b0316610623565b5f805f9054906101000a90046001600160a01b03166001600160a01b0316634665bcda6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104dd573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061050191906120ee565b9050806001600160a01b03166384d810626040518163ffffffff1660e01b81526004016020604051808303815f875af1158015610540573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061056491906120ee565b5060405163a38406a360e01b81523060048201526001600160a01b0382169063a38406a390602401602060405180830381865afa1580156105a7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105cb91906120ee565b600180546001600160a01b0319166001600160a01b0392909216918217905560405130907fcdc82cfed67d9b46d3a15dd3b48745fb894a354d554cb5da5fb8c440f85c108e905f90a350506001546001600160a01b03165b61064c60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b90565b5f5460405163d02e02cf60e01b81523360048201526001600160a01b039091169063d02e02cf90602401602060405180830381865afa158015610694573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b89190612109565b6106d55760405163304ac09760e01b815260040160405180910390fd5b600154604051633f65cf1960e01b81526001600160a01b0390911690633f65cf1990610713908b908b908b908b908b908b908b908b906004016122c3565b5f604051808303815f87803b15801561072a575f80fd5b505af115801561073c573d5f803e3d5ffd5b505050505f5b858110156108c4575f6107a88484848181106107605761076061237b565b9050602002810190610772919061238f565b808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250611a1f92505050565b6001600160401b031690508787838181106107c5576107c561237b565b90506020020160208101906107da91906123d4565b604080516001600160401b038d1681526020810184905264ffffffffff92909216917fc893ff408bc759105622c2fb34c5fa34a9d25f376cae386441e090e9d1ce1c3e910160405180910390a26801bc16d674ec80000060055f8282546108419190612401565b9091555088905087838181106108595761085961237b565b905060200201602081019061086e91906123d4565b604080516001600160401b038d1681526020810184905264ffffffffff92909216917f95636f5f7adc353e75700ba4b3473fe8e0fdba943a168a06699b6e2a8c283d9a910160405180910390a250600101610742565b505050505050505050565b5f546001600160a01b031633146108f9576040516346f94cdd60e11b815260040160405180910390fd5b5f80516020612818833981519152805460029190600160401b900460ff168061092f575080546001600160401b03808416911610155b1561094d5760405163f92ee8a960e01b815260040160405180910390fd5b805468ffffffffffffffffff19166001600160401b038316908117600160401b1782556005849055815460ff60401b191682556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050565b5f806109e560017fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d51612401565b5f1b90505f815490505f819050806001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a2e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5291906120ee565b935050505090565b5f5460405163def8c18960e01b81523360048201526001600160a01b039091169063def8c18990602401602060405180830381865afa158015610a9f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ac39190612109565b610ae05760405163c97c994760e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5491906120ee565b60405163eea9064b60e01b81529091506001600160a01b0382169063eea9064b90610b8790879087908790600401612414565b5f604051808303815f87803b158015610b9e575f80fd5b505af1158015610bb0573d5f803e3d5ffd5b50505050836001600160a01b03167fa6ca69be1634c9486160d4fa9f11c9bf604a6a4b1fd23c8336ffc5889ef4b5ab83604051610bef91815260200190565b60405180910390a250505050565b5f805460408051632332de6d60e11b8152905183926001600160a01b031691634665bcda9160048083019260209291908290030181865afa158015610c44573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c6891906120ee565b6040516360f4062b60e01b81523060048201529091505f906001600160a01b038316906360f4062b90602401602060405180830381865afa158015610caf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cd3919061248b565b600654600554600454610ce691906124a2565b610cf091906124a2565b610cfa91906124b5565b90505f811215610d0c575f9250505090565b92915050565b80516001600160a01b038116610d3b5760405163d92e233d60e01b815260040160405180910390fd5b5f805160206128188339815191528054600160401b810460ff1615906001600160401b03165f81158015610d6c5750825b90505f826001600160401b03166001148015610d875750303b155b905081158015610d95575080155b15610db35760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610ddd57845460ff60401b1916600160401b1785555b610de5611aa5565b86515f80546001600160a01b0319166001600160a01b0390921691909117905560208701516002558315610e5357845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f546001600160a01b03163314610e86576040516346f94cdd60e11b815260040160405180910390fd5b600454811115610eb8576004805460405163d60a51ff60e01b8152918201839052602482015260440160405180910390fd5b6004546040805183815260208101929092527f2b349f43a65f38c08fc62d88fd2750e2ff318ddb2f862facc55cc4c98ac351d9910160405180910390a18060045f828254610f069190612401565b90915550505f80546040516001600160a01b039091169083908381818185875af1925050503d805f8114610f55576040519150601f19603f3d011682016040523d82523d5f602084013e610f5a565b606091505b5050905080610f7c576040516312171d8360e31b815260040160405180910390fd5b5050565b5f54604051630dc3e02960e01b81523360048201526060916001600160a01b031690630dc3e02990602401602060405180830381865afa158015610fc6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fea9190612109565b61100757604051639ed46c1f60e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611057573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107b91906120ee565b6040805160018082528183019092529192505f9190816020015b604080516060808201835280825260208201525f918101919091528152602001906001900390816110955750506040805160018082528183019092529192505f91906020808301908036833750506040805160018082528183019092529293505f9291506020808301908036833701905050905073beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0825f815181106111305761113061237b565b60200260200101906001600160a01b031690816001600160a01b03168152505085815f815181106111635761116361237b565b6020026020010181815250506040518060600160405280838152602001828152602001306001600160a01b0316815250835f815181106111a5576111a561237b565b60209081029190910101526040516306ec6e8160e11b81526001600160a01b03851690630dd8dd02906111dc908690600401612514565b5f604051808303815f875af11580156111f7573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261121e91908101906125a0565b94508560065f82825461123191906124a2565b90915550506040517f6001451f3352c7eb034f00744cc4a30b8a353c5b3a6115559440f7911435d87690611268908890889061262b565b60405180910390a150505050919050565b5f5460405163d02e02cf60e01b81523360048201526001600160a01b039091169063d02e02cf90602401602060405180830381865afa1580156112be573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112e29190612109565b6112ff5760405163304ac09760e01b815260040160405180910390fd5b6001546040516388676cad60e01b815282151560048201526001600160a01b03909116906388676cad906024015f604051808303815f87803b158015611343575f80fd5b505af1158015611355573d5f803e3d5ffd5b5050505050565b5f5460405163def8c18960e01b81523360048201526001600160a01b039091169063def8c18990602401602060405180830381865afa1580156113a1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113c59190612109565b6113e25760405163c97c994760e01b815260040160405180910390fd5b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611432573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061145691906120ee565b604051631976849960e21b81523060048201529091505f906001600160a01b038316906365da126490602401602060405180830381865afa15801561149d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114c191906120ee565b6040519091506001600160a01b038216907f42176493fdfcada70cc1bcf321c9a2314e9571a9fe53c54a5385a1eeac8bc1d7905f90a26040516336a2fa1960e21b81523060048201526001600160a01b0383169063da8be864906024015f604051808303815f875af1158015611539573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261156091908101906125a0565b505050565b5f6115845f80516020612818833981519152546001600160401b031690565b905090565b5f54604051630dc3e02960e01b81523360048201526001600160a01b0390911690630dc3e02990602401602060405180830381865afa1580156115ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115f29190612109565b61160f57604051639ed46c1f60e01b815260040160405180910390fd5b5f8083516001600160401b0381111561162a5761162a611c5d565b604051908082528060200260200182016040528015611653578160200160208202803683370190505b5090505f84516001600160401b0381111561167057611670611c5d565b6040519080825280602002602001820160405280156116a357816020015b606081526020019060019003908161168e5790505b5090505f5b85518110156117d15760018382815181106116c5576116c561237b565b6020026020010190151590811515815250508581815181106116e9576116e961237b565b602002602001015160a00151516001600160401b0381111561170d5761170d611c5d565b604051908082528060200260200182016040528015611736578160200160208202803683370190505b508282815181106117495761174961237b565b60200260200101819052505f5b8682815181106117685761176861237b565b602002602001015160c00151518110156117c85786828151811061178e5761178e61237b565b602002602001015160c0015181815181106117ab576117ab61237b565b6020026020010151856117be91906124a2565b9450600101611756565b506001016116a8565b505f805f9054906101000a90046001600160a01b03166001600160a01b031663ea4d3c9b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611822573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061184691906120ee565b6040516319a021cb60e11b815290915047906001600160a01b0383169063334043969061187d908a9087908b908a90600401612738565b5f604051808303815f87803b158015611894575f80fd5b505af11580156118a6573d5f803e3d5ffd5b504792505f91506118b990508383612401565b90508660065f8282546118cc9190612401565b925050819055508060045f8282546118e491906124a2565b90915550506040517f8366d2488f13298ba3a5415b491f26008d1cf4fc377b05a30a26958d16288e099061191d908b908a9085906127f3565b60405180910390a1505050505050505050565b5f546001600160a01b0316331461195a576040516346f94cdd60e11b815260040160405180910390fd5b60055460408051918252602082018390527f0993f0703b1d00b4bfb9c89b808b7d29986b127f1d0e543c318f82957ac109f8910160405180910390a18060055f8282546119a791906124a2565b909155505050565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f008054600119016119f357604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b5f610d0c82600281518110611a3657611a3661237b565b602002602001015160f881901c60e882901c61ff00161760d882901c62ff0000161760c882901c63ff000000161764ff0000000060b883901c161765ff000000000060a883901c161766ff000000000000609883901c161767ff0000000000000060889290921c919091161790565b611aad611ab7565b611ab5611aed565b565b5f8051602061281883398151915254600160401b900460ff16611ab557604051631afcd79f60e31b815260040160405180910390fd5b6119f9611ab7565b5f60408284031215611b05575f80fd5b50919050565b5f8083601f840112611b1b575f80fd5b5081356001600160401b03811115611b31575f80fd5b6020830191508360208260051b8501011115611b4b575f80fd5b9250929050565b5f805f805f805f8060a0898b031215611b69575f80fd5b88356001600160401b038082168214611b80575f80fd5b90985060208a01359080821115611b95575f80fd5b611ba18c838d01611af5565b985060408b0135915080821115611bb6575f80fd5b611bc28c838d01611b0b565b909850965060608b0135915080821115611bda575f80fd5b611be68c838d01611b0b565b909650945060808b0135915080821115611bfe575f80fd5b50611c0b8b828c01611b0b565b999c989b5096995094979396929594505050565b5f60208284031215611c2f575f80fd5b5035919050565b6001600160a01b0381168114611c4a575f80fd5b50565b8035611c5881611c36565b919050565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715611c9357611c93611c5d565b60405290565b60405160e081016001600160401b0381118282101715611c9357611c93611c5d565b604051601f8201601f191681016001600160401b0381118282101715611ce357611ce3611c5d565b604052919050565b5f805f60608486031215611cfd575f80fd5b8335611d0881611c36565b92506020848101356001600160401b0380821115611d24575f80fd5b9086019060408289031215611d37575f80fd5b611d3f611c71565b823582811115611d4d575f80fd5b8301601f81018a13611d5d575f80fd5b803583811115611d6f57611d6f611c5d565b611d81601f8201601f19168701611cbb565b93508084528a86828401011115611d96575f80fd5b80868301878601375f908401860152509081529082013591810191909152929592945050506040919091013590565b5f60408284031215611dd5575f80fd5b611ddd611c71565b8235611de881611c36565b81526020928301359281019290925250919050565b5f815180845260208085019450602084015f5b83811015611e2c57815187529582019590820190600101611e10565b509495945050505050565b602081525f611e496020830184611dfd565b9392505050565b8015158114611c4a575f80fd5b5f60208284031215611e6d575f80fd5b8135611e4981611e50565b5f6001600160401b03821115611e9057611e90611c5d565b5060051b60200190565b803563ffffffff81168114611c58575f80fd5b5f82601f830112611ebc575f80fd5b81356020611ed1611ecc83611e78565b611cbb565b8083825260208201915060208460051b870101935086841115611ef2575f80fd5b602086015b84811015611f17578035611f0a81611c36565b8352918301918301611ef7565b509695505050505050565b5f82601f830112611f31575f80fd5b81356020611f41611ecc83611e78565b8083825260208201915060208460051b870101935086841115611f62575f80fd5b602086015b84811015611f175780358352918301918301611f67565b5f8060408385031215611f8f575f80fd5b82356001600160401b0380821115611fa5575f80fd5b818501915085601f830112611fb8575f80fd5b81356020611fc8611ecc83611e78565b82815260059290921b84018101918181019089841115611fe6575f80fd5b8286015b848110156120c157803586811115612000575f80fd5b870160e0818d03601f19011215612015575f80fd5b61201d611c99565b612028868301611c4d565b815261203660408301611c4d565b8682015261204660608301611c4d565b60408201526080820135606082015261206160a08301611e9a565b608082015260c08083013589811115612078575f80fd5b6120868f8983870101611ead565b60a08401525060e08301358981111561209d575f80fd5b6120ab8f8983870101611f22565b9183019190915250845250918301918301611fea565b50965050860135925050808211156120d7575f80fd5b506120e485828601611f22565b9150509250929050565b5f602082840312156120fe575f80fd5b8151611e4981611c36565b5f60208284031215612119575f80fd5b8151611e4981611e50565b5f808335601e19843603018112612139575f80fd5b83016020810192503590506001600160401b03811115612157575f80fd5b803603821315611b4b575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b803564ffffffffff81168114611c58575f80fd5b5f838385526020808601955060208560051b830101845f5b878110156121f357848303601f190189526121d48288612124565b6121df858284612165565b9a86019a94505050908301906001016121b9565b5090979650505050505050565b8183525f6001600160fb1b03831115612217575f80fd5b8260051b80836020870137939093016020019392505050565b8183526020808401935f91600585811b8301820185855b888110156122b557858303601f19018a52813536899003601e1901811261226c575f80fd5b880185810190356001600160401b03811115612286575f80fd5b80861b3603821315612296575f80fd5b6122a1858284612200565b9b87019b9450505090840190600101612247565b509098975050505050505050565b6001600160401b03891681525f602060a06020840152893560a08401526122ed60208b018b612124565b604060c086015261230260e086018284612165565b85810360408701528a81528b925060200190505f5b8a8110156123415764ffffffffff61232e8461218d565b1682529183019190830190600101612317565b50848103606086015261235581898b6121a1565b92505050828103608084015261236c818587612230565b9b9a5050505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e198436030181126123a4575f80fd5b8301803591506001600160401b038211156123bd575f80fd5b6020019150600581901b3603821315611b4b575f80fd5b5f602082840312156123e4575f80fd5b611e498261218d565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610d0c57610d0c6123ed565b60018060a01b03841681525f60206060602084015284516040606085015280518060a08601525f5b818110156124585782810184015186820160c00152830161243c565b505f60c082870101526020870151608086015260c0601f19601f8301168601019350505050826040830152949350505050565b5f6020828403121561249b575f80fd5b5051919050565b80820180821115610d0c57610d0c6123ed565b8082018281125f8312801582168215821617156124d4576124d46123ed565b505092915050565b5f815180845260208085019450602084015f5b83811015611e2c5781516001600160a01b0316875295820195908201906001016124ef565b5f60208083018184528085518083526040925060408601915060408160051b8701018488015f5b838110156122b557603f19898403018552815160608151818652612561828701826124dc565b915050888201518582038a8701526125798282611dfd565b928901516001600160a01b031695890195909552509487019492509086019060010161253b565b5f60208083850312156125b1575f80fd5b82516001600160401b038111156125c6575f80fd5b8301601f810185136125d6575f80fd5b80516125e4611ecc82611e78565b81815260059190911b82018301908381019087831115612602575f80fd5b928401925b8284101561262057835182529284019290840190612607565b979650505050505050565b828152604060208201525f6126436040830184611dfd565b949350505050565b5f82825180855260208086019550808260051b8401018186015f5b848110156121f357858303601f19018952815180516001600160a01b03908116855285820151811686860152604080830151909116908501526060808201519085015260808082015163ffffffff169085015260a08082015160e082870181905291906126d5838801826124dc565b9250505060c080830151925085820381870152506126f38183611dfd565b9a86019a9450505090830190600101612666565b5f815180845260208085019450602084015f5b83811015611e2c57815115158752958201959082019060010161271a565b608081525f61274a608083018761264b565b6020838203818501528187518084528284019150828160051b850101838a015f5b838110156127c757868303601f190185528151805180855290870190878501905f5b818110156127b25783516001600160a01b03168352928901929189019160010161278d565b5050958701959350509085019060010161276b565b505086810360408801526127db818a611dfd565b94505050505082810360608401526126208185612707565b606081525f612805606083018661264b565b6020830194909452506040015291905056fef0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220b522996afc7ca4b85bbbfb8bec7d9486f91dd1577bd80271d51c9af514ec2fe364736f6c63430008180033", + "nonce": "0xcf", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xdcc537", + "logs": [ + { + "address": "0xa8bd8f089f5bd0bee61e23b3a0a4aa1332fe929d", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xc479445a397e937daa0e658e0c6a408670c676a6e9899f186969517d4f0e9dc4", + "blockNumber": "0x13fb2ba", + "transactionHash": "0xa7b95e51c3329685699a8f6e7596848a039c514212da7ddf979c9459f598469e", + "transactionIndex": "0xa4", + "logIndex": "0x154", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000004000000000000000000000000400000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xa7b95e51c3329685699a8f6e7596848a039c514212da7ddf979c9459f598469e", + "transactionIndex": "0xa4", + "blockHash": "0xc479445a397e937daa0e658e0c6a408670c676a6e9899f186969517d4f0e9dc4", + "blockNumber": "0x13fb2ba", + "gasUsed": "0x235cb0", + "effectiveGasPrice": "0x1c97e77ac", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0xa8bd8f089f5bd0bee61e23b3a0a4aa1332fe929d" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728762860, + "chain": 1, + "commit": "aef108d" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNodesManager-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNodesManager-1.json new file mode 100644 index 000000000..60c63a5d4 --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/StakingNodesManager-1.json @@ -0,0 +1,60 @@ +{ + "transactions": [ + { + "hash": "0x2e8de640ac1289d10af77335bfc53038ef002d4467ce792909e4cbb384d9d28f", + "transactionType": "CREATE", + "contractName": "StakingNodesManager", + "contractAddress": "0x33ffb713c73cdf668b11296bb9b1c9a0c3100626", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x50e4e6", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61486c80620000e35f395ff3fe608060405260043610620002fe575f3560e01c806391d14854116200018e578063cb70bd7911620000ea578063def8c189116200009e578063f33de6c01162000076578063f33de6c01462000a23578063f3513a371462000a39578063fb1bb9de1462000a5f578063ff50abdc1462000a94575f80fd5b8063def8c18914620009a9578063e63ab1e914620009cd578063ea4d3c9b1462000a02575f80fd5b8063cb70bd7914620008ee578063cd3cddb31462000905578063d02e02cf1462000929578063d2609520146200094d578063d27e80e8146200096e578063d547741f1462000985575f80fd5b8063ad7805e81162000142578063ad7805e8146200080a578063b1b623d8146200083f578063ba4eb2c41462000874578063ba8b9761146200088f578063c108f0cb14620008a6578063c9fd18b314620008ca575f80fd5b806391d14854146200073e5780639872ab8414620007625780639ab739d314620007835780639e2eaadb14620007b8578063a1174e7d14620007cf578063a217fddf14620007f5575f80fd5b806336568abe116200025a5780635fc90c56116200020e5780637d1f8a8111620001e65780637d1f8a8114620006bc57806382699d5a14620006e257806384f9b77a1462000703578063863928511462000727575f80fd5b80635fc90c56146200063f57806361464d2d146200067457806361669d271462000698575f80fd5b806336568abe146200057157806339b70e3814620005955780633f2a554014620005b65780634665bcda14620005d7578063555344df14620005f75780635f8ad023146200061b575f80fd5b8063239d85ab11620002b2578063239d85ab1462000478578063248a9ca3146200049e5780632b5cfa8114620004c25780632b828f5c14620004f55780632f2ff15d146200051957806335aa2e44146200053d575f80fd5b806301ffc9a7146200034257806304f67aa1146200037b5780630dc3e02914620003ad57806315d36e7214620003d15780631c53c28014620004065780632118a4bb1462000443575f80fd5b366200033e57604080513381523460208201527fbfe611b001dfcd411432f7bf0d79b82b4b2ee81511edac123a3403c357fb972a910160405180910390a1005b5f80fd5b3480156200034e575f80fd5b50620003666200036036600462003435565b62000aab565b60405190151581526020015b60405180910390f35b34801562000387575f80fd5b506200039e5f80516020620047f783398151915281565b60405190815260200162000372565b348015620003b9575f80fd5b5062000366620003cb36600462003473565b62000ae2565b348015620003dd575f80fd5b506200039e7f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf75881565b34801562000412575f80fd5b506200042a6200042436600462003491565b62000b0f565b6040516001600160a01b03909116815260200162000372565b3480156200044f575f80fd5b506200039e7ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c0638581565b34801562000484575f80fd5b506200049c6200049636600462003473565b62000b38565b005b348015620004aa575f80fd5b506200039e620004bc36600462003491565b62000c98565b348015620004ce575f80fd5b50620004e6620004e036600462003473565b62000cb9565b604051620003729190620034fa565b34801562000501575f80fd5b506200049c620005133660046200350e565b62000cfe565b34801562000525575f80fd5b506200049c6200053736600462003525565b62000f4b565b34801562000549575f80fd5b50620005616200055b36600462003491565b62000f73565b6040516200037292919062003556565b3480156200057d575f80fd5b506200049c6200058f36600462003525565b62001031565b348015620005a1575f80fd5b506004546200042a906001600160a01b031681565b348015620005c2575f80fd5b506007546200042a906001600160a01b031681565b348015620005e3575f80fd5b505f546200042a906001600160a01b031681565b34801562000603575f80fd5b506200049c6200061536600462003579565b6200106c565b34801562000627575f80fd5b506200049c6200063936600462003579565b620010dd565b3480156200064b575f80fd5b506200039e7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff508881565b34801562000680575f80fd5b506200049c6200069236600462003491565b620012fe565b348015620006a4575f80fd5b50620004e6620006b636600462003491565b62001354565b348015620006c8575f80fd5b50600c546200042a9061010090046001600160a01b031681565b348015620006ee575f80fd5b506001546200042a906001600160a01b031681565b3480156200070f575f80fd5b506200049c62000721366004620035ec565b620013f3565b6200049c62000738366004620035fe565b62001611565b3480156200074a575f80fd5b50620003666200075c36600462003525565b6200167f565b3480156200076e575f80fd5b506006546200042a906001600160a01b031681565b3480156200078f575f80fd5b506200039e7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b81565b348015620007c4575f80fd5b506200042a620016b6565b348015620007db575f80fd5b50620007e66200188c565b60405162000372919062003626565b34801562000801575f80fd5b506200039e5f81565b34801562000816575f80fd5b506200039e7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9881565b3480156200084b575f80fd5b506200039e7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d581565b34801562000880575f80fd5b50600c54620003669060ff1681565b3480156200089b575f80fd5b506200049c620018ee565b348015620008b2575f80fd5b506200049c620008c4366004620036e6565b62001961565b348015620008d6575f80fd5b506200039e620008e836600462003809565b620019c9565b348015620008fa575f80fd5b506200039e60095481565b34801562000911575f80fd5b506200049c6200092336600462003473565b620019e5565b34801562000935575f80fd5b50620003666200094736600462003473565b62001af3565b34801562000959575f80fd5b506005546200042a906001600160a01b031681565b3480156200097a575f80fd5b506200049c62001b20565b34801562000991575f80fd5b506200049c620009a336600462003525565b62001b87565b348015620009b5575f80fd5b5062000366620009c736600462003473565b62001ba9565b348015620009d9575f80fd5b506200039e7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b34801562000a0e575f80fd5b506002546200042a906001600160a01b031681565b34801562000a2f575f80fd5b506008546200039e565b34801562000a45575f80fd5b5062000a5062001bd6565b604051620003729190620038fb565b34801562000a6b575f80fd5b506200039e7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b34801562000aa0575f80fd5b506200039e62001cd5565b5f6001600160e01b03198216637965db0b60e01b148062000adc57506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f62000adc7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b836200167f565b6008818154811062000b1f575f80fd5b5f918252602090912001546001600160a01b0316905081565b5f80516020620047f783398151915262000b528162001e21565b816001600160a01b03811662000b7b5760405163d92e233d60e01b815260040160405180910390fd5b6005546001600160a01b031662000ba557604051636aef646b60e11b815260040160405180910390fd5b600554604051631b2ce7f360e11b81526001600160a01b03858116600483015290911690633659cfe6906024015f604051808303815f87803b15801562000bea575f80fd5b505af115801562000bfd573d5f803e3d5ffd5b505060085491505f90505b8181101562000c4f5762000c466008828154811062000c2b5762000c2b62003975565b5f918252602090912001546001600160a01b03168362001e30565b60010162000c08565b50604080516001600160a01b0386168152602081018390527f02c79c1e3f8b682a9487f84feb1ab388b1ebf7a5e85307e941ec19122d2ed14e910160405180910390a150505050565b5f9081525f8051602062004817833981519152602052604090206001015490565b60408051600160f81b60208201525f6021820152606083811b6bffffffffffffffffffffffff1916602c83015291016040516020818303038152906040529050919050565b62000d0d602082018262003473565b6001600160a01b03811662000d355760405163d92e233d60e01b815260040160405180910390fd5b62000d47604083016020840162003473565b6001600160a01b03811662000d6f5760405163d92e233d60e01b815260040160405180910390fd5b62000d81606084016040850162003473565b6001600160a01b03811662000da95760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805460029190600160401b900460ff168062000df3575080546001600160401b03808416911610155b1562000e125760405163f92ee8a960e01b815260040160405180910390fd5b805468ffffffffffffffffff19166001600160401b03831617600160401b1781555f62000e3f8162001e21565b62000e4e602088018862003473565b600c80546001600160a01b039290921661010002610100600160a81b031990921691909117905562000eb27fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9862000eac60408a0160208b0162003473565b62002039565b5062000eea7f1ce621c530dfade3239478366c4e5e2c9d62462d265362544aa805bfede55f8b62000eac60608a0160408b0162003473565b5050600380546001600160a01b0319169055805460ff60401b191681556040516001600160401b03831681527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050505050565b62000f568262000c98565b62000f618162001e21565b62000f6d838362002039565b50505050565b600a818154811062000f83575f80fd5b905f5260205f2090600202015f91509050805f01805462000fa49062003989565b80601f016020809104026020016040519081016040528092919081815260200182805462000fd29062003989565b8015620010215780601f1062000ff75761010080835404028352916020019162001021565b820191905f5260205f20905b8154815290600101906020018083116200100357829003601f168201915b5050505050908060010154905082565b6001600160a01b03811633146200105b5760405163334bd91960e11b815260040160405180910390fd5b620010678282620020e1565b505050565b6008545f5b8281101562000f6d575f84848381811062001090576200109062003975565b9050602002810190620010a49190620039bd565b606001359050828110620010d357604051630fd3716f60e01b8152600481018290526024015b60405180910390fd5b5060010162001071565b7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d5620011098162001e21565b620011136200215f565b600c5460ff16156200113857604051634ecaf21960e11b815260040160405180910390fd5b5f8290036200115a5760405163f0a35a8760e01b815260040160405180910390fd5b6200116683836200106c565b5f6200117c6801bc16d674ec80000084620039f0565b6006546040516378a1085360e11b8152600481018390529192506001600160a01b03169063f14210a6906024015f604051808303815f87803b158015620011c1575f80fd5b505af1158015620011d4573d5f803e3d5ffd5b508592505f9150505b81811015620012d15736868683818110620011fc57620011fc62003975565b9050602002810190620012109190620039bd565b9050600b62001220828062003a0a565b6040516200123092919062003a4f565b9081526040519081900360200190205460ff1615620012725762001255818062003a0a565b60405163055ce5e960e01b8152600401620010ca92919062003a86565b6001600b62001282838062003a0a565b6040516200129292919062003a4f565b908152604051908190036020019020805491151560ff19909216919091179055620012c7816801bc16d674ec800000620021aa565b50600101620011dd565b5050506200106760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b5f80516020620047f7833981519152620013188162001e21565b60098290556040518281527f5179ce38a6079ec731d2e265edcf9e1635e8ac7fd689ad42c5e20a7571c054159060200160405180910390a15050565b60605f600883815481106200136d576200136d62003975565b5f9182526020918290200154604080516351d5709b60e11b815290516001600160a01b039092169263a3aae136926004808401938290030181865afa158015620013b9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620013df919062003aa3565b9050620013ec8162000cb9565b9392505050565b620014076101408201610120830162003473565b6001600160a01b0381166200142f5760405163d92e233d60e01b815260040160405180910390fd5b620014436101608301610140840162003473565b6001600160a01b0381166200146b5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f81158015620014b05750825b90505f826001600160401b03166001148015620014cc5750303b155b905081158015620014db575080155b15620014fa5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156200152557845460ff60401b1916600160401b1785555b6200152f6200242f565b620015396200243b565b62001544886200244f565b6200154f8862002779565b6200156361016089016101408a0162003473565b600780546001600160a01b0319166001600160a01b0392909216919091179055610100880135600955620015a061014089016101208a0162003473565b600680546001600160a01b0319166001600160a01b039290921691909117905583156200160757845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b336001600160a01b03166008838154811062001631576200163162003975565b5f918252602090912001546001600160a01b0316146200166e57604051635975744160e01b815233600482015260248101839052604401620010ca565b6200167b8282346200293f565b5050565b5f9182525f8051602062004817833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b6005545f906001600160a01b031680620016e35760405163d92e233d60e01b815260040160405180910390fd5b7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff50886200170f8162001e21565b60085460095481106200173c57600954604051634ec828df60e11b8152600401620010ca91815260200190565b6005546040515f916001600160a01b031690620017599062003419565b6001600160a01b0390911681526040602082018190525f90820152606001604051809103905ff08015801562001791573d5f803e3d5ffd5b50905080620017a1818462001e30565b5f816001600160a01b0316630b10b2016040518163ffffffff1660e01b81526004016020604051808303815f875af1158015620017e0573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001806919062003aa3565b600880546001810182555f9182527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30180546001600160a01b0319166001600160a01b038681169182179092556040519394509084169290917fd343f0ba00451215cbdbdbcd008daa9a218952bce2ba4e283e72d722f0a2ce0791a35094505050505090565b60606008805480602002602001604051908101604052809291908181526020018280548015620018e457602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311620018c5575b5050505050905090565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6200191a8162001e21565b600c805460ff191660019081179091556040519081527f73da136f9aaeb50b1d3304d0410c5b6bf28e6003ad23ab5d3a8b0df1c1c8d583906020015b60405180910390a150565b7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe986200198d8162001e21565b5f5b82518110156200106757620019c0838281518110620019b257620019b262003975565b602002602001015162002b0e565b6001016200198f565b5f620019da87878787878762002e47565b979650505050505050565b5f80516020620047f7833981519152620019ff8162001e21565b816001600160a01b03811662001a285760405163d92e233d60e01b815260040160405180910390fd5b6005546001600160a01b03161562001a535760405163a81fbfef60e01b815260040160405180910390fd5b823060405162001a639062003427565b6001600160a01b03928316815291166020820152604001604051809103905ff08015801562001a94573d5f803e3d5ffd5b50600580546001600160a01b0319166001600160a01b039283169081179091556040805191825291851660208201527fd13f13f7d12bbc1ec3c2f99ed663d6b643f722a4654c11d4325b35ed88d9fb75910160405180910390a1505050565b5f62000adc7ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c06385836200167f565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a62001b4c8162001e21565b600c805460ff191690556040515f81527f73da136f9aaeb50b1d3304d0410c5b6bf28e6003ad23ab5d3a8b0df1c1c8d5839060200162001956565b62001b928262000c98565b62001b9d8162001e21565b62000f6d8383620020e1565b5f62000adc7f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf758836200167f565b6060600a805480602002602001604051908101604052809291908181526020015f905b8282101562001ccc578382905f5260205f2090600202016040518060400160405290815f8201805462001c2c9062003989565b80601f016020809104026020016040519081016040528092919081815260200182805462001c5a9062003989565b801562001ca95780601f1062001c7f5761010080835404028352916020019162001ca9565b820191905f5260205f20905b81548152906001019060200180831162001c8b57829003601f168201915b505050505081526020016001820154815250508152602001906001019062001bf9565b50505050905090565b6008545f9081805b8281101562001d85576008818154811062001cfc5762001cfc62003975565b5f918252602091829020015460408051630dd28e5360e31b815290516001600160a01b0390921692636e947298926004808401938290030181865afa15801562001d48573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001d6e919062003ac1565b62001d7a908362003ad9565b915060010162001cdd565b50600c5461010090046001600160a01b03161562000adc57600c60019054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001def573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e15919062003ac1565b620013ec908262003ad9565b62001e2d8133620031c6565b50565b5f826001600160a01b031663b3c650156040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001e6e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e94919062003aef565b9050806001600160401b03165f0362001fcc576040805180820182523081526020810184815291516377b3492760e01b815290516001600160a01b03908116600483015291516024820152908416906377b34927906044015f604051808303815f87803b15801562001f04575f80fd5b505af115801562001f17573d5f803e3d5ffd5b50505050826001600160a01b031663b3c650156040518163ffffffff1660e01b8152600401602060405180830381865afa15801562001f58573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001f7e919062003aef565b604080516001600160a01b03861681526001600160401b03831660208201529192507fa070c83df7efc58b668afb64705222890e09b4090d56ee9981fc66ac1c552aa1910160405180910390a15b806001600160401b031660010362001067576040516316d384a360e21b81525f60048201526001600160a01b03841690635b4e128c906024015f604051808303815f87803b1580156200201d575f80fd5b505af115801562002030573d5f803e3d5ffd5b50505050505050565b5f5f80516020620048178339815191526200205584846200167f565b620020d7575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556200208c3390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505062000adc565b5f91505062000adc565b5f5f8051602062004817833981519152620020fd84846200167f565b15620020d7575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505062000adc565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805460011901620021a457604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b60608201355f620021bb8262001354565b90505f620021e5620021ce868062003a0a565b620021dd602089018962003a0a565b868962002e47565b9050846040013581146200221a5760408051630d2f5c5b60e41b815260048101839052908601356024820152604401620010ca565b6001546001600160a01b031663228951188562002238888062003a0a565b866200224860208c018c62003a0a565b886040518863ffffffff1660e01b81526004016200226c9695949392919062003b17565b5f604051808303818588803b15801562002284575f80fd5b505af115801562002297573d5f803e3d5ffd5b5050505050600a604051806040016040528087805f0190620022ba919062003a0a565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250938552505050606089013560209283015283546001810185559381522081519192600202019081906200231d908262003bba565b506020820151816001015550506008838154811062002340576200234062003975565b5f9182526020909120015460405163f99d9e5560e01b8152600481018690526001600160a01b039091169063f99d9e55906024015f604051808303815f87803b1580156200238c575f80fd5b505af11580156200239f573d5f803e3d5ffd5b505050507fdc72e80912284a6826bc0379c720da32bc7d9f8bef5caceb8c1408d7582e191983868060200190620023d7919062003a0a565b620023e3898062003a0a565b8688604051620023fa979695949392919062003c86565b60405180910390a15050505050565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6200243962003203565b565b6200244562003203565b620024396200324d565b6200245e602082018262003473565b6001600160a01b038116620024865760405163d92e233d60e01b815260040160405180910390fd5b62002498604083016020840162003473565b6001600160a01b038116620024c05760405163d92e233d60e01b815260040160405180910390fd5b620024d2606084016040850162003473565b6001600160a01b038116620024fa5760405163d92e233d60e01b815260040160405180910390fd5b6200250c60a085016080860162003473565b6001600160a01b038116620025345760405163d92e233d60e01b815260040160405180910390fd5b6200254660c0860160a0870162003473565b6001600160a01b0381166200256e5760405163d92e233d60e01b815260040160405180910390fd5b6200258060e0870160c0880162003473565b6001600160a01b038116620025a85760405163d92e233d60e01b815260040160405180910390fd5b620025bb610100880160e0890162003473565b6001600160a01b038116620025e35760405163d92e233d60e01b815260040160405180910390fd5b620025f75f62000eac60208b018b62003473565b506200261d5f80516020620047f783398151915262000eac60408b0160208c0162003473565b50620026557f2a3e51343906b7d96ede4f504667876289b489c8291cb3728f6505c8853bf75862000eac60808b0160608c0162003473565b506200268d7f87421e189bd94dc1673f0d5255fa9f0cb8ff65bb74e34e0a80b07e9f0b4e34d562000eac60a08b0160808c0162003473565b50620026c57ffc5002b0ddb2d30bcbc72c36a5a6e5db445f5ea1509f389f2698d1cf20c0638562000eac60608b0160408c0162003473565b50620026fd7ffbad6f4d3f31ae36729ac2baa91ec12b01628098ce3f5ec489f254ef90ff508862000eac60c08b0160a08c0162003473565b50620027357f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a62000eac60e08b0160c08c0162003473565b506200276e7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a62000eac6101008b0160e08c0162003473565b505050505050505050565b6200278d6101808201610160830162003473565b6001600160a01b038116620027b55760405163d92e233d60e01b815260040160405180910390fd5b620027c96101a08301610180840162003473565b6001600160a01b038116620027f15760405163d92e233d60e01b815260040160405180910390fd5b620028056101c084016101a0850162003473565b6001600160a01b0381166200282d5760405163d92e233d60e01b815260040160405180910390fd5b620028416101e085016101c0860162003473565b6001600160a01b038116620028695760405163d92e233d60e01b815260040160405180910390fd5b6200287d6101808601610160870162003473565b600180546001600160a01b0319166001600160a01b0392909216919091179055620028b16101a08601610180870162003473565b5f80546001600160a01b0319166001600160a01b0392909216919091179055620028e46101c086016101a0870162003473565b600280546001600160a01b0319166001600160a01b0392909216919091179055620029186101e086016101c0870162003473565b600480546001600160a01b0319166001600160a01b03929092169190911790555050505050565b5f600183600181111562002957576200295762003ce0565b03620029dc5760075f9054906101000a90046001600160a01b03166001600160a01b031663453ba4886040518163ffffffff1660e01b8152600401602060405180830381865afa158015620029ae573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620029d4919062003aa3565b905062002a66565b5f836001811115620029f257620029f262003ce0565b0362002a495760075f9054906101000a90046001600160a01b03166001600160a01b0316631d9082886040518163ffffffff1660e01b8152600401602060405180830381865afa158015620029ae573d5f803e3d5ffd5b8260405163586dcbff60e01b8152600401620010ca919062003d15565b5f816001600160a01b0316836040515f6040518083038185875af1925050503d805f811462002ab1576040519150601f19603f3d011682016040523d82523d5f602084013e62002ab6565b606091505b505090508062002ad9576040516312171d8360e31b815260040160405180910390fd5b7f469ab84da5ea1952fdc0b8e86ec64bc27f9aa12eee7f41f7817fc282b13174fb858585604051620023fa9392919062003d25565b80516020820151604083015160608401515f8162002b2d848662003ad9565b62002b39919062003ad9565b90505f6008868154811062002b525762002b5262003975565b5f918252602090912001546040516379cb2d2f60e01b8152600481018490526001600160a01b03909116915081906379cb2d2f906024015f604051808303815f87803b15801562002ba1575f80fd5b505af115801562002bb4573d5f803e3d5ffd5b505050505f85111562002c285760065f9054906101000a90046001600160a01b03166001600160a01b0316631ef2aac5866040518263ffffffff1660e01b81526004015f604051808303818588803b15801562002c0f575f80fd5b505af115801562002c22573d5f803e3d5ffd5b50505050505b831562002caa57600c546040515f9161010090046001600160a01b03169086908381818185875af1925050503d805f811462002c80576040519150601f19603f3d011682016040523d82523d5f602084013e62002c85565b606091505b505090508062002ca8576040516312171d8360e31b815260040160405180910390fd5b505b821562002df657600754604080516308a7749160e31b815290515f926001600160a01b03169163453ba4889160048083019260209291908290030181865afa15801562002cf9573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062002d1f919062003aa3565b6001600160a01b0316846040515f6040518083038185875af1925050503d805f811462002d68576040519150601f19603f3d011682016040523d82523d5f602084013e62002d6d565b606091505b505090508062002d90576040516312171d8360e31b815260040160405180910390fd5b60075f9054906101000a90046001600160a01b03166001600160a01b031663f9fc0d076040518163ffffffff1660e01b81526004015f604051808303815f87803b15801562002ddd575f80fd5b505af115801562002df0573d5f803e3d5ffd5b50505050505b6040805187815260208101879052908101859052606081018490527f862fa866e2defc0ce54e39f7f30e76dc382859ffdaad1909d80281216e3a6ccd9060800160405180910390a150505050505050565b5f8062002e59633b9aca008462003d49565b90505f62002e678262003257565b90505f60028a8a5f60801b60405160200162002e869392919062003d69565b60408051601f198184030181529082905262002ea29162003d90565b602060405180830381855afa15801562002ebe573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002ee3919062003ac1565b90505f60028062002ef86040848c8e62003da3565b60405160200162002f0b92919062003a4f565b60408051601f198184030181529082905262002f279162003d90565b602060405180830381855afa15801562002f43573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002f68919062003ac1565b600262002f798b6040818f62003da3565b60405162002f8e9291905f9060200162003dcc565b60408051601f198184030181529082905262002faa9162003d90565b602060405180830381855afa15801562002fc6573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062002feb919062003ac1565b60408051602081019390935282015260600160408051601f1981840301815290829052620030199162003d90565b602060405180830381855afa15801562003035573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906200305a919062003ac1565b905060028083896040516020016200307492919062003dde565b60408051601f1981840301815290829052620030909162003d90565b602060405180830381855afa158015620030ac573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190620030d1919062003ac1565b604051600290620030eb9087905f90879060200162003e05565b60408051601f1981840301815290829052620031079162003d90565b602060405180830381855afa15801562003123573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019062003148919062003ac1565b60408051602081019390935282015260600160408051601f1981840301815290829052620031769162003d90565b602060405180830381855afa15801562003192573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190620031b7919062003ac1565b9b9a5050505050505050505050565b620031d282826200167f565b6200167b5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401620010ca565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166200243957604051631afcd79f60e31b815260040160405180910390fd5b6200240962003203565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b825f8151811062003299576200329962003975565b60200101906001600160f81b03191690815f1a9053508060061a60f81b82600181518110620032cc57620032cc62003975565b60200101906001600160f81b03191690815f1a9053508060051a60f81b82600281518110620032ff57620032ff62003975565b60200101906001600160f81b03191690815f1a9053508060041a60f81b8260038151811062003332576200333262003975565b60200101906001600160f81b03191690815f1a9053508060031a60f81b8260048151811062003365576200336562003975565b60200101906001600160f81b03191690815f1a9053508060021a60f81b8260058151811062003398576200339862003975565b60200101906001600160f81b03191690815f1a9053508060011a60f81b82600681518110620033cb57620033cb62003975565b60200101906001600160f81b03191690815f1a905350805f1a60f81b82600781518110620033fd57620033fd62003975565b60200101906001600160f81b03191690815f1a90535050919050565b6105978062003e3f83390190565b61042180620043d683390190565b5f6020828403121562003446575f80fd5b81356001600160e01b031981168114620013ec575f80fd5b6001600160a01b038116811462001e2d575f80fd5b5f6020828403121562003484575f80fd5b8135620013ec816200345e565b5f60208284031215620034a2575f80fd5b5035919050565b5f5b83811015620034c5578181015183820152602001620034ab565b50505f910152565b5f8151808452620034e6816020860160208601620034a9565b601f01601f19169290920160200192915050565b602081525f620013ec6020830184620034cd565b5f606082840312156200351f575f80fd5b50919050565b5f806040838503121562003537575f80fd5b8235915060208301356200354b816200345e565b809150509250929050565b604081525f6200356a6040830185620034cd565b90508260208301529392505050565b5f80602083850312156200358b575f80fd5b82356001600160401b0380821115620035a2575f80fd5b818501915085601f830112620035b6575f80fd5b813581811115620035c5575f80fd5b8660208260051b8501011115620035da575f80fd5b60209290920196919550909350505050565b5f6101e082840312156200351f575f80fd5b5f806040838503121562003610575f80fd5b823591506020830135600281106200354b575f80fd5b602080825282518282018190525f9190848201906040850190845b81811015620036685783516001600160a01b03168352928401929184019160010162003641565b50909695505050505050565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715620036ad57620036ad62003674565b60405290565b604051601f8201601f191681016001600160401b0381118282101715620036de57620036de62003674565b604052919050565b5f6020808385031215620036f8575f80fd5b82356001600160401b03808211156200370f575f80fd5b818501915085601f83011262003723575f80fd5b81358181111562003738576200373862003674565b62003748848260051b01620036b3565b818152848101925060079190911b83018401908782111562003768575f80fd5b928401925b81841015620019da576080848903121562003786575f80fd5b6200379062003688565b84358152858501358682015260408086013590820152606080860135908201528352608090930192918401916200376d565b5f8083601f840112620037d3575f80fd5b5081356001600160401b03811115620037ea575f80fd5b60208301915083602082850101111562003802575f80fd5b9250929050565b5f805f805f80608087890312156200381f575f80fd5b86356001600160401b038082111562003836575f80fd5b620038448a838b01620037c2565b90985096506020915088820135818111156200385e575f80fd5b6200386c8b828c01620037c2565b90975095505060408901358181111562003884575f80fd5b8901601f81018b1362003895575f80fd5b803582811115620038aa57620038aa62003674565b620038be601f8201601f19168501620036b3565b92508083528b84828401011115620038d4575f80fd5b80848301858501375f84828501015250508093505050606087013590509295509295509295565b5f60208083018184528085518083526040925060408601915060408160051b8701018488015f5b838110156200396757888303603f19018552815180518785526200394988860182620034cd565b91890151948901949094529487019492509086019060010162003922565b509098975050505050505050565b634e487b7160e01b5f52603260045260245ffd5b600181811c908216806200399e57607f821691505b6020821081036200351f57634e487b7160e01b5f52602260045260245ffd5b5f8235607e19833603018112620039d2575f80fd5b9190910192915050565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141762000adc5762000adc620039dc565b5f808335601e1984360301811262003a20575f80fd5b8301803591506001600160401b0382111562003a3a575f80fd5b60200191503681900382131562003802575f80fd5b818382375f9101908152919050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f62003a9b60208301848662003a5e565b949350505050565b5f6020828403121562003ab4575f80fd5b8151620013ec816200345e565b5f6020828403121562003ad2575f80fd5b5051919050565b8082018082111562000adc5762000adc620039dc565b5f6020828403121562003b00575f80fd5b81516001600160401b0381168114620013ec575f80fd5b608081525f62003b2c60808301888a62003a5e565b828103602084015262003b408188620034cd565b9050828103604084015262003b5781868862003a5e565b915050826060830152979650505050505050565b601f8211156200106757805f5260205f20601f840160051c8101602085101562003b925750805b601f840160051c820191505b8181101562003bb3575f815560010162003b9e565b5050505050565b81516001600160401b0381111562003bd65762003bd662003674565b62003bee8162003be7845462003989565b8462003b6b565b602080601f83116001811462003c24575f841562003c0c5750858301515b5f19600386901b1c1916600185901b17855562003c7e565b5f85815260208120601f198616915b8281101562003c545788860151825594840194600190910190840162003c33565b508582101562003c7257878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b87815260a060208201525f62003ca160a08301888a62003a5e565b828103604084015262003cb681878962003a5e565b9050846060840152828103608084015262003cd28185620034cd565b9a9950505050505050505050565b634e487b7160e01b5f52602160045260245ffd5b6002811062003d1157634e487b7160e01b5f52602160045260245ffd5b9052565b6020810162000adc828462003cf4565b8381526060810162003d3b602083018562003cf4565b826040830152949350505050565b5f8262003d6457634e487b7160e01b5f52601260045260245ffd5b500490565b828482376fffffffffffffffffffffffffffffffff19919091169101908152601001919050565b5f8251620039d2818460208701620034a9565b5f808585111562003db2575f80fd5b8386111562003dbf575f80fd5b5050820193919092039150565b82848237909101908152602001919050565b8281525f825162003df7816020850160208701620034a9565b919091016020019392505050565b5f845162003e18818460208901620034a9565b67ffffffffffffffff19949094169190930190815260188101919091526038019291505056fe60a060405260405161059738038061059783398101604081905261002291610376565b61002c828261003e565b506001600160a01b0316608052610465565b610047826100fb565b6040516001600160a01b038316907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e905f90a28051156100ef576100ea826001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100e49190610431565b82610209565b505050565b6100f761027c565b5050565b806001600160a01b03163b5f0361013557604051631933b43b60e21b81526001600160a01b03821660048201526024015b60405180910390fd5b807fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5080546001600160a01b0319166001600160a01b0392831617905560408051635c60da1b60e01b815290515f92841691635c60da1b9160048083019260209291908290030181865afa1580156101ae573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101d29190610431565b9050806001600160a01b03163b5f036100f757604051634c9c8ce360e01b81526001600160a01b038216600482015260240161012c565b60605f80846001600160a01b031684604051610225919061044a565b5f60405180830381855af49150503d805f811461025d576040519150601f19603f3d011682016040523d82523d5f602084013e610262565b606091505b50909250905061027385838361029d565b95945050505050565b341561029b5760405163b398979f60e01b815260040160405180910390fd5b565b6060826102b2576102ad826102fc565b6102f5565b81511580156102c957506001600160a01b0384163b155b156102f257604051639996b31560e01b81526001600160a01b038516600482015260240161012c565b50805b9392505050565b80511561030c5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b80516001600160a01b038116811461033b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b8381101561036e578181015183820152602001610356565b50505f910152565b5f8060408385031215610387575f80fd5b61039083610325565b60208401519092506001600160401b03808211156103ac575f80fd5b818501915085601f8301126103bf575f80fd5b8151818111156103d1576103d1610340565b604051601f8201601f19908116603f011681019083821181831017156103f9576103f9610340565b81604052828152886020848701011115610411575f80fd5b610422836020830160208801610354565b80955050505050509250929050565b5f60208284031215610441575f80fd5b6102f582610325565b5f825161045b818460208701610354565b9190910192915050565b60805161011b61047c5f395f601d015261011b5ff3fe6080604052600a600c565b005b60186014601a565b609d565b565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156076573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906098919060ba565b905090565b365f80375f80365f845af43d5f803e80801560b6573d5ff35b3d5ffd5b5f6020828403121560c9575f80fd5b81516001600160a01b038116811460de575f80fd5b939250505056fea26469706673582212202b8e47f9dbadb5e5b182ef67478f04e8f051fa0445601c532c82f3537586c39564736f6c63430008180033608060405234801561000f575f80fd5b5060405161042138038061042183398101604081905261002e9161015f565b806001600160a01b03811661005d57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61006681610077565b50610070826100c6565b5050610190565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b806001600160a01b03163b5f036100fb5760405163211eb15960e21b81526001600160a01b0382166004820152602401610054565b600180546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b80516001600160a01b038116811461015a575f80fd5b919050565b5f8060408385031215610170575f80fd5b61017983610144565b915061018760208401610144565b90509250929050565b6102848061019d5f395ff3fe608060405234801561000f575f80fd5b5060043610610055575f3560e01c80633659cfe6146100595780635c60da1b1461006e578063715018a6146100975780638da5cb5b1461009f578063f2fde38b146100af575b5f80fd5b61006c610067366004610221565b6100c2565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006c6100d6565b5f546001600160a01b031661007b565b61006c6100bd366004610221565b6100e9565b6100ca610128565b6100d381610154565b50565b6100de610128565b6100e75f6101d2565b565b6100f1610128565b6001600160a01b03811661011f57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6100d3816101d2565b5f546001600160a01b031633146100e75760405163118cdaa760e01b8152336004820152602401610116565b806001600160a01b03163b5f036101895760405163211eb15960e21b81526001600160a01b0382166004820152602401610116565b600180546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f60208284031215610231575f80fd5b81356001600160a01b0381168114610247575f80fd5b939250505056fea26469706673582212205220934962511be7ea7a268c3ed5e444d6b1301ddf31d457c8b666be316f12a864736f6c6343000818003324791c44c040514a5d2580696fc45e7d3cb6c9fa65bf3db2e4755362d6c155b502dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a2646970667358221220ad669ff9dd3726d3637456fe0a46008f4ea459d5e8589ec96d59bd0b53e241ac64736f6c63430008180033", + "nonce": "0xcd", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x5b9b51", + "logs": [ + { + "address": "0x33ffb713c73cdf668b11296bb9b1c9a0c3100626", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xada76e6d29a01586f549baf7ac045559790566d078beeb26c73496c87bbe5a66", + "blockNumber": "0x13fb262", + "transactionHash": "0x2e8de640ac1289d10af77335bfc53038ef002d4467ce792909e4cbb384d9d28f", + "transactionIndex": "0xc", + "logIndex": "0x57", + "removed": false + } + ], + "logsBloom": "0x00020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x2e8de640ac1289d10af77335bfc53038ef002d4467ce792909e4cbb384d9d28f", + "transactionIndex": "0xc", + "blockHash": "0xada76e6d29a01586f549baf7ac045559790566d078beeb26c73496c87bbe5a66", + "blockNumber": "0x13fb262", + "gasUsed": "0x3e39ec", + "effectiveGasPrice": "0x21608d426", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x33ffb713c73cdf668b11296bb9b1c9a0c3100626" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728761808, + "chain": 1, + "commit": "296a852" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-implementation-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-implementation-1.json new file mode 100644 index 000000000..c1d55b681 --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-implementation-1.json @@ -0,0 +1,59 @@ +{ + "transactions": [ + { + "hash": "0x9b1560b39029838262e55d5617e43e13be973cf667272666029483a3e9b00706", + "transactionType": "CREATE", + "contractName": "WithdrawalQueueManager", + "contractAddress": "0x6ac38ab99053a2585b6184d0ad72018ae22185cd", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x475d45", + "value": "0x0", + "input": "0x608060405234801562000010575f80fd5b506200001b62000021565b620000d5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000725760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d25780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b613f8b80620000e35f395ff3fe608060405234801561000f575f80fd5b5060043610610388575f3560e01c80637d1f8a81116101df578063ac1e502511610109578063dbbfd152116100a9578063e6dc4f1611610079578063e6dc4f1614610824578063e985e9c514610837578063efdcd9741461084a578063f84694e11461085d575f80fd5b8063dbbfd152146107f6578063e26dc432146107fe578063e51aa67814610811578063e63a391f1461081a575f80fd5b8063b88d4fde116100e4578063b88d4fde14610771578063bbeea20b14610784578063c87b56dd146107d0578063d547741f146107e3575f80fd5b8063ac1e502514610743578063b3f0067414610756578063b6dddc8314610769575f80fd5b80639c69c3471161017f578063a08ab5c31161014f578063a08ab5c314610702578063a217fddf14610715578063a22cb4651461071c578063aae733581461072f575f80fd5b80639c69c347146106a25780639dea569d146106b55780639ee679e8146106c8578063a024d723146106db575f80fd5b80638c4724cc116101ba5780638c4724cc1461065a57806391d1485414610662578063937b25811461067557806395d89b411461069a575f80fd5b80637d1f8a811461063557806384c4bd4b146106485780638bc7e8c414610651575f80fd5b80632f745c59116102c05780634718d52711610260578063612c1fa011610230578063612c1fa0146105d65780636352211e146105fd57806370a0823114610610578063748f9ef014610623575f80fd5b80634718d527146105405780634bac05a9146105825780634f6ccce7146105a35780635209cb98146105b6575f80fd5b80633569d58a1161029b5780633569d58a1461050757806336568abe146105105780633dd31e0e1461052357806342842e0e1461052d575f80fd5b80632f745c59146104ce5780633384d9fb146104e157806334e73122146104f4575f80fd5b80630b7019d91161032b57806318160ddd1161030657806318160ddd1461048157806323b872dd14610495578063248a9ca3146104a85780632f2ff15d146104bb575f80fd5b80630b7019d9146104445780630cc2250f1461045b578063115949211461046e575f80fd5b80630770fa91116103665780630770fa91146103de578063081812fc146103f35780630913b1211461041e578063095ea7b314610431575f80fd5b806301ffc9a71461038c57806306fdde03146103b4578063071fc512146103c9575b5f80fd5b61039f61039a366004613384565b610870565b60405190151581526020015b60405180910390f35b6103bc61089a565b6040516103ab91906133ec565b61039f6103d73660046133fe565b6009541190565b6103f16103ec3660046133fe565b61093b565b005b6104066104013660046133fe565b610a38565b6040516001600160a01b0390911681526020016103ab565b6103f161042c36600461345c565b610a4c565b6103f161043f3660046134e1565b610ad2565b61044d60045481565b6040519081526020016103ab565b6103f16104693660046135d0565b610ae1565b6103f161047c3660046136e4565b610df2565b5f80516020613ed68339815191525461044d565b6103f16104a3366004613743565b610e1c565b61044d6104b63660046133fe565b610ea5565b6103f16104c9366004613781565b610ec5565b61044d6104dc3660046134e1565b610ee1565b6103f16104ef366004613781565b610f52565b61044d6105023660046137af565b610fa9565b61044d60095481565b6103f161051e366004613781565b610fc9565b61044d6224ea0081565b6103f161053b366004613743565b611001565b61055361054e3660046133fe565b61101b565b604080516001600160401b0394851681529390921660208401526001600160601b0316908201526060016103ab565b6105956105903660046137cf565b61105e565b6040516103ab929190613832565b61044d6105b13660046133fe565b611294565b6105c96105c43660046133fe565b611306565b6040516103ab91906138c8565b61044d7f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e81565b61040661060b3660046133fe565b611427565b61044d61061e3660046137cf565b611431565b5f54610406906001600160a01b031681565b600154610406906001600160a01b031681565b61044d60025481565b61044d60055481565b61044d611489565b61039f610670366004613781565b611526565b6106886106833660046133fe565b61155c565b6040516103ab969594939291906138da565b6103bc61161f565b61044d6106b03660046137af565b61165d565b600754610406906001600160a01b031681565b61044d6106d63660046133fe565b6116e6565b61044d7f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead9581565b61044d610710366004613918565b6116fb565b61044d5f81565b6103f161072a36600461395b565b6118cb565b61044d5f80516020613f3683398151915281565b6103f16107513660046133fe565b6118d6565b600654610406906001600160a01b031681565b600a5461044d565b6103f161077f36600461398b565b61194d565b6107976107923660046133fe565b611965565b6040805182516001600160401b03908116825260208085015190911690820152918101516001600160601b0316908201526060016103ab565b6103bc6107de3660046133fe565b611a0b565b6103f16107f1366004613781565b611a7b565b61044d611a97565b6103f161080c3660046139f2565b611b26565b61044d60085481565b61044d620f424081565b61044d6108323660046133fe565b611b6d565b61039f610845366004613a60565b611da5565b6103f16108583660046137cf565b611df1565b61044d61086b3660046133fe565b611e8d565b5f6001600160e01b031982166380ac58cd60e01b1480610894575061089482611fc6565b92915050565b5f80516020613eb683398151915280546060919081906108b990613a8c565b80601f01602080910402602001604051908101604052809291908181526020018280546108e590613a8c565b80156109305780601f1061090757610100808354040283529160200191610930565b820191905f5260205f20905b81548152906001019060200180831161091357829003601f168201915b505050505091505090565b7f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e61096581611fea565b5f61096e611a97565b9050808311156109a057604051630bf0035360e31b815260048101849052602481018290526044015b60405180910390fd5b600154604051632e31641160e11b8152600481018590526001600160a01b0390911690635c62c822906024015f604051808303815f87803b1580156109e3575f80fd5b505af11580156109f5573d5f803e3d5ffd5b505060408051868152602081018590527f5471a21865bef29e2f9817eeb3138f749523b6e76a3c880a65fdd61d35604ac8935001905060405180910390a1505050565b5f610a4282611ff4565b506108948261202b565b828114610a7657604051631f4bb7c160e31b81526004810184905260248101829052604401610997565b5f5b83811015610acb57610ac3858583818110610a9557610a95613ac4565b90506020020135848484818110610aae57610aae613ac4565b90506020020160208101906104ef91906137cf565b600101610a78565b5050505050565b610add828233612064565b5050565b60808101516001600160a01b038116610b0d5760405163d92e233d60e01b815260040160405180910390fd5b60408201516001600160a01b038116610b395760405163d92e233d60e01b815260040160405180910390fd5b60608301516001600160a01b038116610b655760405163d92e233d60e01b815260040160405180910390fd5b60a08401516001600160a01b038116610b915760405163d92e233d60e01b815260040160405180910390fd5b6101008501516001600160a01b038116610bbe5760405163d92e233d60e01b815260040160405180910390fd5b6101208601516001600160a01b038116610beb5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f81158015610c2f5750825b90505f826001600160401b03166001148015610c4a5750303b155b905081158015610c58575080155b15610c765760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610ca057845460ff60401b1916600160401b1785555b610cb18c5f01518d60200151612071565b60408c01515f80546001600160a01b039283166001600160a01b031991821617825560608f0151600180549190941691161790915560808d0151610cf59190612083565b50610d115f80516020613f368339815191528d60a00151612083565b50610d407f16e47d7ccdc4d9916471674b101198c830d9d6666d46998629b3ca840510386e8d60c00151612083565b50610d707f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead958d6101200151612083565b5060e08c01516005556101008c0151600680546001600160a01b0319166001600160a01b039092169190911790558315610de457845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050505050505050565b610dfa612124565b610e038161215b565b610e1960015f80516020613f1683398151915255565b50565b6001600160a01b038216610e4557604051633250574960e11b81525f6004820152602401610997565b5f610e518383336126c5565b9050836001600160a01b0316816001600160a01b031614610e9f576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401610997565b50505050565b5f9081525f80516020613ef6833981519152602052604090206001015490565b610ece82610ea5565b610ed781611fea565b610e9f8383612083565b5f5f80516020613e96833981519152610ef984611431565b8310610f2a5760405163295f44f760e21b81526001600160a01b038516600482015260248101849052604401610997565b6001600160a01b0384165f908152602091825260408082208583529092522054905092915050565b610f5a612124565b5f6040518060600160405280848152602001610f7585611e8d565b8152602001836001600160a01b03168152509050610f928161215b565b50610add60015f80516020613f1683398151915255565b5f620f4240610fb88385613aec565b610fc29190613b03565b9392505050565b6001600160a01b0381163314610ff25760405163334bd91960e11b815260040160405180910390fd5b610ffc82826127c3565b505050565b610ffc83838360405180602001604052805f81525061194d565b600a818154811061102a575f80fd5b5f918252602090912001546001600160401b038082169250600160401b82041690600160801b90046001600160601b031683565b6060805f61106b84611431565b9050805f036110b757604080515f808252602082018181528284019093529091906110ac565b61109961333c565b8152602001906001900390816110915790505b509250925050915091565b806001600160401b038111156110cf576110cf61350b565b6040519080825280602002602001820160405280156110f8578160200160208202803683370190505b509250806001600160401b038111156111135761111361350b565b60405190808252806020026020018201604052801561114c57816020015b61113961333c565b8152602001906001900390816111315790505b5091505f5b8181101561128d575f6111648683610ee1565b90508085838151811061117957611179613ac4565b6020908102919091018101919091525f8281526003808352604091829020825160c081018452815481526001820154948101949094526002810154928401929092528101546060830152600481015460ff161515608083015260058101805460a0840191906111e790613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461121390613a8c565b801561125e5780601f106112355761010080835404028352916020019161125e565b820191905f5260205f20905b81548152906001019060200180831161124157829003601f168201915b50505050508152505084838151811061127957611279613ac4565b602090810291909101015250600101611151565b5050915091565b5f5f80516020613e968339815191526112b85f80516020613ed68339815191525490565b83106112e05760405163295f44f760e21b81525f600482015260248101849052604401610997565b8060020183815481106112f5576112f5613ac4565b905f5260205f200154915050919050565b61130e61333c565b5f82815260036020818152604092839020835160c08101855281548152600182015492810192909252600281015493820193909352908201546060820152600482015460ff161515608082015260058201805491929160a08401919061137390613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461139f90613a8c565b80156113ea5780601f106113c1576101008083540402835291602001916113ea565b820191905f5260205f20905b8154815290600101906020018083116113cd57829003601f168201915b50505050508152505090506114028160600151151590565b61142257604051630169130760e01b815260048101839052602401610997565b919050565b5f61089482611ff4565b5f5f80516020613eb68339815191526001600160a01b038316611469576040516322718ad960e21b81525f6004820152602401610997565b6001600160a01b039092165f908152600390920160205250604090205490565b5f8060015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114db573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114ff9190613b22565b905080600854111561151f57806008546115199190613b39565b91505090565b5f91505090565b5f9182525f80516020613ef6833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b600360208190525f9182526040909120805460018201546002830154938301546004840154600585018054949693959394929360ff9092169261159e90613a8c565b80601f01602080910402602001604051908101604052809291908181526020018280546115ca90613a8c565b80156116155780601f106115ec57610100808354040283529160200191611615565b820191905f5260205f20905b8154815290600101906020018083116115f857829003601f168201915b5050505050905086565b7f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930180546060915f80516020613eb6833981519152916108b990613a8c565b5f805f9054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116ad573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116d19190613b4c565b6116dc90600a613c4c565b610fb88385613aec565b5f6108948260405180602001604052805f8152505b5f611704612124565b825f0361172457604051635e85ae7360e01b815260040160405180910390fd5b5f5461173b906001600160a01b031633308661283c565b6001546040805163540385a360e01b815290515f926001600160a01b03169163540385a39160048083019260209291908290030181865afa158015611782573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117a69190613b22565b600280549192505f6117b783613c5a565b909155506040805160c08101825286815260058054602080840191825283850187815242606086019081525f6080870181815260a088018d81528a83526003958690529890912087518155945160018601559151600285015551918301919091555160048201805460ff1916911515919091179055925193955090928392918201906118439082613cb6565b50905050611851858361165d565b60085f8282546118619190613d71565b9091555061187190503384612896565b336001600160a01b0316837f470a96c327e4382aa8e66bc975ce38a8e3eacb9c8e4d87d34e8dc4a853539de6836040516118ab91906138c8565b60405180910390a3505061089460015f80516020613f1683398151915255565b610add3383836128f7565b5f80516020613f368339815191526118ed81611fea565b620f4240821115611911576040516314afa02960e21b815260040160405180910390fd5b60058290556040518281527fe94edcc114da5287ecb30ee8aacf12e6c16b11b8583d7f9e477ab1616e33d53d9060200160405180910390a15050565b611958848484610e1c565b610e9f33858585856129a6565b604080516060810182525f8082526020820181905291810191909152600a5482106119a55760405162a6e6ff60e61b815260048101839052602401610997565b600a82815481106119b8576119b8613ac4565b5f9182526020918290206040805160608101825291909201546001600160401b038082168352600160401b820416938201939093526001600160601b03600160801b909304929092169082015292915050565b6060611a1682611ff4565b505f611a2c60408051602081019091525f815290565b90505f815111611a4a5760405180602001604052805f815250610fc2565b80611a5484612acd565b604051602001611a65929190613d84565b6040516020818303038152906040529392505050565b611a8482610ea5565b611a8d81611fea565b610e9f83836127c3565b5f8060015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ae9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b0d9190613b22565b905060085481111561151f576008546115199082613b39565b5f5b81811015610ffc575f838383818110611b4357611b43613ac4565b905060600201803603810190611b5991906136e4565b9050611b6481610df2565b50600101611b28565b5f7f8586893ec6c8c067a87b0dc34e01d210b44bbdce9d41cb027cc2d7c0f31ead95611b9881611fea565b6001546040805163540385a360e01b815290515f926001600160a01b03169163540385a39160048083019260209291908290030181865afa158015611bdf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c039190613b22565b90505f6040518060600160405280611c1c600954612b5c565b6001600160401b03168152602001611c3387612b5c565b6001600160401b03168152602001611c4a84612b95565b6001600160601b03908116909152600a80546001810182555f9190915282517fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a88201805460208601516040870151909516600160801b026bffffffffffffffffffffffff60801b196001600160401b03968716600160401b026fffffffffffffffffffffffffffffffff199093169690941695909517179190911692909217909155600254909550909150851115611d23576002546040516326af3c0960e21b8152610997918791600401918252602082015260400190565b6009548511611d535760095460405163b6c78a0360e01b8152610997918791600401918252602082015260400190565b600954604080518781526020810192909252810183905284907fec80db79f7dde8aa35235328c05a056f900bc38a0eb09fe40868458bef7963a09060600160405180910390a250505060099190915590565b6001600160a01b039182165f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab00793056020908152604080832093909416825291909152205460ff1690565b806001600160a01b038116611e195760405163d92e233d60e01b815260040160405180910390fd5b5f80516020613f36833981519152611e3081611fea565b6006546040516001600160a01b038086169216907fa92ff4390fe6943f0b30e8fe715dde86f85ab79b2b2c640a10fc094cc4036cc8905f90a35050600680546001600160a01b0319166001600160a01b0392909216919091179055565b600a545f90808203611eb557604051635f5010c560e11b815260048101849052602401610997565b5f80611ec2600184613b39565b90505b808211611faa575f6002611ed98385613d71565b611ee39190613b03565b90505f600a8281548110611ef957611ef9613ac4565b5f9182526020918290206040805160608101825292909101546001600160401b03808216808552600160401b830490911694840194909452600160801b90046001600160601b03169082015291508710801590611f62575080602001516001600160401b031687105b15611f71575095945050505050565b80516001600160401b0316871015611f9557611f8e600183613b39565b9250611fa3565b611fa0826001613d71565b93505b5050611ec5565b604051635f5010c560e11b815260048101869052602401610997565b5f6001600160e01b03198216637965db0b60e01b1480610894575061089482612bc8565b610e198133612bec565b5f80611fff83612c25565b90506001600160a01b03811661089457604051637e27328960e01b815260048101849052602401610997565b5f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930460205260409020546001600160a01b031690565b610ffc8383836001612c5e565b612079612d71565b610add8282612dbc565b5f5f80516020613ef683398151915261209c8484611526565b61211b575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556120d13390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a46001915050610894565b5f915050610894565b5f80516020613f1683398151915280546001190161215557604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b8051602082015160408301513361217184612c25565b6001600160a01b03161415801561219c5750835133906121909061202b565b6001600160a01b031614155b156121c6578351604051636a931be560e11b81526004810191909152336024820152604401610997565b600a5482106121ea5760405162a6e6ff60e61b815260048101839052602401610997565b5f600a83815481106121fe576121fe613ac4565b5f9182526020918290206040805160608101825292909101546001600160401b03808216808552600160401b830490911694840194909452600160801b90046001600160601b0316908201529150841080612266575080602001516001600160401b03168410155b156122ac578051602082015160405163d0fca86360e01b815260048101879052602481018690526001600160401b03928316604482015291166064820152608401610997565b6040808201515f86815260036020818152848320855160c08101875281548152600182015492810192909252600281015495820195909552908401546060820152600484015460ff16151560808201526005840180546001600160601b0390941694929391929160a08401919061232290613a8c565b80601f016020809104026020016040519081016040528092919081815260200182805461234e90613a8c565b80156123995780601f1061237057610100808354040283529160200191612399565b820191905f5260205f20905b81548152906001019060200180831161237c57829003601f168201915b50505050508152505090506123b18160600151151590565b6123d157604051630169130760e01b815260048101879052602401610997565b8060800151156123f7576040516331d5519160e01b815260048101879052602401610997565b612402866009541190565b61242257604051635f5010c560e11b815260048101879052602401610997565b5f86815260036020526040808220600401805460ff19166001179055820151831161244d5782612453565b81604001515b90505f612463835f01518361165d565b90508060085f8282546124769190613b39565b90915550612485905088612dec565b5f548351604051630852cd8d60e31b81526001600160a01b03909216916342966c68916124b89160040190815260200190565b5f604051808303815f87803b1580156124cf575f80fd5b505af11580156124e1573d5f803e3d5ffd5b505050505f6124f4828560200151610fa9565b90505f60015f9054906101000a90046001600160a01b03166001600160a01b031663acd08aa76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612547573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061256b9190613b22565b9050828110156125985760405163cf47918160e01b81526004810182905260248101849052604401610997565b6001546001600160a01b031663d2edb68f896125b48587613b39565b8860a001516040518463ffffffff1660e01b81526004016125d793929190613db2565b5f604051808303815f87803b1580156125ee575f80fd5b505af1158015612600573d5f803e3d5ffd5b505050505f8211156126765760015460065460a087015160405163d2edb68f60e01b81526001600160a01b039384169363d2edb68f9361264893911691879190600401613db2565b5f604051808303815f87803b15801561265f575f80fd5b505af1158015612671573d5f803e3d5ffd5b505050505b897f043063fdf65eda59c2092782b3f6984485c435525b12b85fd117d2bd7e2d9a5c338a888d888a6040516126b096959493929190613de1565b60405180910390a25050505050505050505050565b5f806126d2858585612e24565b90506001600160a01b03811661275957612754845f80516020613ed683398151915280545f8381527f645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0360205260408120829055600182018355919091527fa42f15e5d656f8155fd7419d740a6073999f19cd6e061449ce4a257150545bf20155565b61277c565b846001600160a01b0316816001600160a01b03161461277c5761277c8185612f26565b6001600160a01b0385166127985761279384612fbd565b6127bb565b846001600160a01b0316816001600160a01b0316146127bb576127bb858561308a565b949350505050565b5f5f80516020613ef68339815191526127dc8484611526565b1561211b575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a46001915050610894565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610e9f9085906130e2565b6001600160a01b0382166128bf57604051633250574960e11b81525f6004820152602401610997565b5f6128cb83835f6126c5565b90506001600160a01b03811615610ffc576040516339e3563760e11b81525f6004820152602401610997565b5f80516020613eb68339815191526001600160a01b03831661293757604051630b61174360e31b81526001600160a01b0384166004820152602401610997565b6001600160a01b038481165f818152600584016020908152604080832094881680845294825291829020805460ff191687151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a350505050565b6001600160a01b0383163b15610acb57604051630a85bd0160e11b81526001600160a01b0384169063150b7a02906129e8908890889087908790600401613e2a565b6020604051808303815f875af1925050508015612a22575060408051601f3d908101601f19168201909252612a1f91810190613e66565b60015b612a89573d808015612a4f576040519150601f19603f3d011682016040523d82523d5f602084013e612a54565b606091505b5080515f03612a8157604051633250574960e11b81526001600160a01b0385166004820152602401610997565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b14612ac557604051633250574960e11b81526001600160a01b0385166004820152602401610997565b505050505050565b60605f612ad98361314e565b60010190505f816001600160401b03811115612af757612af761350b565b6040519080825280601f01601f191660200182016040528015612b21576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084612b2b57509392505050565b5f6001600160401b03821115612b9157604080516306dfcc6560e41b8152600481019190915260248101839052604401610997565b5090565b5f6001600160601b03821115612b91576040516306dfcc6560e41b81526060600482015260248101839052604401610997565b5f6001600160e01b0319821663780e9d6360e01b1480610894575061089482613225565b612bf68282611526565b610add5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610997565b5f9081527f80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab007930260205260409020546001600160a01b031690565b5f80516020613eb68339815191528180612c8057506001600160a01b03831615155b15612d41575f612c8f85611ff4565b90506001600160a01b03841615801590612cbb5750836001600160a01b0316816001600160a01b031614155b8015612cce5750612ccc8185611da5565b155b15612cf75760405163a9fbf51f60e01b81526001600160a01b0385166004820152602401610997565b8215612d3f5784866001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b5f93845260040160205250506040902080546001600160a01b0319166001600160a01b0392909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16612dba57604051631afcd79f60e31b815260040160405180910390fd5b565b612dc4612d71565b5f80516020613eb683398151915280612ddd8482613cb6565b5060018101610e9f8382613cb6565b5f612df85f835f6126c5565b90506001600160a01b038116610add57604051637e27328960e01b815260048101839052602401610997565b5f5f80516020613eb683398151915281612e3d85612c25565b90506001600160a01b03841615612e5957612e59818587613274565b6001600160a01b03811615612e9557612e745f865f80612c5e565b6001600160a01b0381165f908152600383016020526040902080545f190190555b6001600160a01b03861615612ec5576001600160a01b0386165f9081526003830160205260409020805460010190555b5f85815260028301602052604080822080546001600160a01b0319166001600160a01b038a811691821790925591518893918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a495945050505050565b5f80516020613e968339815191525f612f3e84611431565b5f8481526001840160209081526040808320546001600160a01b03891684529186905290912091925090818314612f96575f838152602082815260408083205485845281842081905583526001870190915290208290555b5f948552600190930160209081526040808620869055928552929092528220919091555050565b5f80516020613ed6833981519152545f80516020613e96833981519152905f90612fe990600190613b39565b5f84815260038401602052604081205460028501805493945090928490811061301457613014613ac4565b905f5260205f20015490508084600201838154811061303557613035613ac4565b5f9182526020808320909101929092558281526003860190915260408082208490558682528120556002840180548061307057613070613e81565b600190038181905f5260205f20015f905590555050505050565b5f80516020613e968339815191525f60016130a485611431565b6130ae9190613b39565b6001600160a01b039094165f9081526020838152604080832087845282528083208690559482526001909301909252502055565b5f8060205f8451602086015f885af180613101576040513d5f823e3d81fd5b50505f513d91508115613118578060011415613125565b6001600160a01b0384163b155b15610e9f57604051635274afe760e01b81526001600160a01b0385166004820152602401610997565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061318c5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106131b8576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106131d657662386f26fc10000830492506010015b6305f5e10083106131ee576305f5e100830492506008015b612710831061320257612710830492506004015b60648310613214576064830492506002015b600a83106108945760010192915050565b5f6001600160e01b031982166380ac58cd60e01b148061325557506001600160e01b03198216635b5e139f60e01b145b8061089457506301ffc9a760e01b6001600160e01b0319831614610894565b61327f8383836132d8565b610ffc576001600160a01b0383166132ad57604051637e27328960e01b815260048101829052602401610997565b60405163177e802f60e01b81526001600160a01b038316600482015260248101829052604401610997565b5f6001600160a01b038316158015906127bb5750826001600160a01b0316846001600160a01b0316148061331157506133118484611da5565b806127bb5750826001600160a01b031661332a8361202b565b6001600160a01b031614949350505050565b6040518060c001604052805f81526020015f81526020015f81526020015f81526020015f15158152602001606081525090565b6001600160e01b031981168114610e19575f80fd5b5f60208284031215613394575f80fd5b8135610fc28161336f565b5f5b838110156133b95781810151838201526020016133a1565b50505f910152565b5f81518084526133d881602086016020860161339f565b601f01601f19169290920160200192915050565b602081525f610fc260208301846133c1565b5f6020828403121561340e575f80fd5b5035919050565b5f8083601f840112613425575f80fd5b5081356001600160401b0381111561343b575f80fd5b6020830191508360208260051b8501011115613455575f80fd5b9250929050565b5f805f806040858703121561346f575f80fd5b84356001600160401b0380821115613485575f80fd5b61349188838901613415565b909650945060208701359150808211156134a9575f80fd5b506134b687828801613415565b95989497509550505050565b6001600160a01b0381168114610e19575f80fd5b8035611422816134c2565b5f80604083850312156134f2575f80fd5b82356134fd816134c2565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b60405161014081016001600160401b03811182821017156135425761354261350b565b60405290565b5f82601f830112613557575f80fd5b81356001600160401b03808211156135715761357161350b565b604051601f8301601f19908116603f011681019082821181831017156135995761359961350b565b816040528381528660208588010111156135b1575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f602082840312156135e0575f80fd5b81356001600160401b03808211156135f6575f80fd5b90830190610140828603121561360a575f80fd5b61361261351f565b823582811115613620575f80fd5b61362c87828601613548565b825250602083013582811115613640575f80fd5b61364c87828601613548565b60208301525061365e604084016134d6565b604082015261366f606084016134d6565b6060820152613680608084016134d6565b608082015261369160a084016134d6565b60a08201526136a260c084016134d6565b60c082015260e083013560e082015261010091506136c18284016134d6565b8282015261012091506136d58284016134d6565b91810191909152949350505050565b5f606082840312156136f4575f80fd5b604051606081018181106001600160401b03821117156137165761371661350b565b806040525082358152602083013560208201526040830135613737816134c2565b60408201529392505050565b5f805f60608486031215613755575f80fd5b8335613760816134c2565b92506020840135613770816134c2565b929592945050506040919091013590565b5f8060408385031215613792575f80fd5b8235915060208301356137a4816134c2565b809150509250929050565b5f80604083850312156137c0575f80fd5b50508035926020909101359150565b5f602082840312156137df575f80fd5b8135610fc2816134c2565b805182526020810151602083015260408101516040830152606081015160608301526080810151151560808301525f60a082015160c060a08501526127bb60c08501826133c1565b604080825283519082018190525f906020906060840190828701845b8281101561386a5781518452928401929084019060010161384e565b50505083810382850152845180825282820190600581901b830184018785015f5b838110156138b957601f198684030185526138a78383516137ea565b9487019492509086019060010161388b565b50909998505050505050505050565b602081525f610fc260208301846137ea565b868152856020820152846040820152836060820152821515608082015260c060a08201525f61390c60c08301846133c1565b98975050505050505050565b5f8060408385031215613929575f80fd5b8235915060208301356001600160401b03811115613945575f80fd5b61395185828601613548565b9150509250929050565b5f806040838503121561396c575f80fd5b8235613977816134c2565b9150602083013580151581146137a4575f80fd5b5f805f806080858703121561399e575f80fd5b84356139a9816134c2565b935060208501356139b9816134c2565b92506040850135915060608501356001600160401b038111156139da575f80fd5b6139e687828801613548565b91505092959194509250565b5f8060208385031215613a03575f80fd5b82356001600160401b0380821115613a19575f80fd5b818501915085601f830112613a2c575f80fd5b813581811115613a3a575f80fd5b866020606083028501011115613a4e575f80fd5b60209290920196919550909350505050565b5f8060408385031215613a71575f80fd5b8235613a7c816134c2565b915060208301356137a4816134c2565b600181811c90821680613aa057607f821691505b602082108103613abe57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761089457610894613ad8565b5f82613b1d57634e487b7160e01b5f52601260045260245ffd5b500490565b5f60208284031215613b32575f80fd5b5051919050565b8181038181111561089457610894613ad8565b5f60208284031215613b5c575f80fd5b815160ff81168114610fc2575f80fd5b600181815b80851115613ba657815f1904821115613b8c57613b8c613ad8565b80851615613b9957918102915b93841c9390800290613b71565b509250929050565b5f82613bbc57506001610894565b81613bc857505f610894565b8160018114613bde5760028114613be857613c04565b6001915050610894565b60ff841115613bf957613bf9613ad8565b50506001821b610894565b5060208310610133831016604e8410600b8410161715613c27575081810a610894565b613c318383613b6c565b805f1904821115613c4457613c44613ad8565b029392505050565b5f610fc260ff841683613bae565b5f60018201613c6b57613c6b613ad8565b5060010190565b601f821115610ffc57805f5260205f20601f840160051c81016020851015613c975750805b601f840160051c820191505b81811015610acb575f8155600101613ca3565b81516001600160401b03811115613ccf57613ccf61350b565b613ce381613cdd8454613a8c565b84613c72565b602080601f831160018114613d16575f8415613cff5750858301515b5f19600386901b1c1916600185901b178555612ac5565b5f85815260208120601f198616915b82811015613d4457888601518255948401946001909101908401613d25565b5085821015613d6157878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561089457610894613ad8565b5f8351613d9581846020880161339f565b835190830190613da981836020880161339f565b01949350505050565b60018060a01b0384168152826020820152606060408201525f613dd860608301846133c1565b95945050505050565b6001600160a01b0387811682528616602082015260c0604082018190525f90613e0c908301876137ea565b606083019590955250608081019290925260a0909101529392505050565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f90613e5c908301846133c1565b9695505050505050565b5f60208284031215613e76575f80fd5b8151610fc28161336f565b634e487b7160e01b5f52603160045260245ffdfe645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0080bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab0079300645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed0202dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268009b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00947478cb7f25f91a774cb9fce49ddd1952e530220912f89fefb6a8e0d8f04381a26469706673582212205241c4d1fe20f348b8047036f3a441befb91619aa43d7d7d612ce62bbb43aa4964736f6c63430008180033", + "nonce": "0xd2", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": null, + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x0bc9bc81ad379810b36ad5cc95387112990aa67b", + "function": null, + "arguments": [ + "0x6ac38ab99053a2585B6184D0AD72018Ae22185CD", + "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", + "0x" + ], + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61030000000000000000000000006ac38ab99053a2585b6184d0ad72018ae22185cd000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d697500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0xd3", + "chainId": "0x1" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0xd3f08f92d6752b01fc5b706adbe254b222a1bdaa", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + } + ], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [], + "pending": [ + "0x9b1560b39029838262e55d5617e43e13be973cf667272666029483a3e9b00706" + ], + "returns": {}, + "timestamp": 1728779119, + "chain": 1, + "commit": "851367c" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-proxy-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-proxy-1.json new file mode 100644 index 000000000..63a976130 --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalQueueManager-proxy-1.json @@ -0,0 +1,99 @@ +{ + "transactions": [ + { + "hash": "0x719a78bbcba746a8beec7b1ce5b469c639e4e75cdb8371f30b7e5c77290a75e8", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x0bc9bc81ad379810b36ad5cc95387112990aa67b", + "function": null, + "arguments": [ + "0x6ac38ab99053a2585B6184D0AD72018Ae22185CD", + "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", + "0x" + ], + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61030000000000000000000000006ac38ab99053a2585b6184d0ad72018ae22185cd000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d697500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0xd3", + "chainId": "0x1" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0xd3f08f92d6752b01fc5b706adbe254b222a1bdaa", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + } + ], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x3e2271", + "logs": [ + { + "address": "0x0bc9bc81ad379810b36ad5cc95387112990aa67b", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x0000000000000000000000006ac38ab99053a2585b6184d0ad72018ae22185cd" + ], + "data": "0x", + "blockHash": "0xcce2fa6bbdb58212c0ac8bd9285683c8f9cde8a03bea4cb47224e35554b2de7d", + "blockNumber": "0x13fb822", + "transactionHash": "0x719a78bbcba746a8beec7b1ce5b469c639e4e75cdb8371f30b7e5c77290a75e8", + "transactionIndex": "0x33", + "logIndex": "0x6b", + "removed": false + }, + { + "address": "0xd3f08f92d6752b01fc5b706adbe254b222a1bdaa", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + ], + "data": "0x", + "blockHash": "0xcce2fa6bbdb58212c0ac8bd9285683c8f9cde8a03bea4cb47224e35554b2de7d", + "blockNumber": "0x13fb822", + "transactionHash": "0x719a78bbcba746a8beec7b1ce5b469c639e4e75cdb8371f30b7e5c77290a75e8", + "transactionIndex": "0x33", + "logIndex": "0x6c", + "removed": false + }, + { + "address": "0x0bc9bc81ad379810b36ad5cc95387112990aa67b", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d3f08f92d6752b01fc5b706adbe254b222a1bdaa", + "blockHash": "0xcce2fa6bbdb58212c0ac8bd9285683c8f9cde8a03bea4cb47224e35554b2de7d", + "blockNumber": "0x13fb822", + "transactionHash": "0x719a78bbcba746a8beec7b1ce5b469c639e4e75cdb8371f30b7e5c77290a75e8", + "transactionIndex": "0x33", + "logIndex": "0x6d", + "removed": false + } + ], + "logsBloom": "0x000000000000000000000000000000004000000000000800008000000000000000000000000000000020000000000000000000000000000000000000100000000200000000000000000080000020020000010000002000080000000000000008000000000200000000000000000008000000008000000000000000000000004000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000402000000000000000020000000020000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x719a78bbcba746a8beec7b1ce5b469c639e4e75cdb8371f30b7e5c77290a75e8", + "transactionIndex": "0x33", + "blockHash": "0xcce2fa6bbdb58212c0ac8bd9285683c8f9cde8a03bea4cb47224e35554b2de7d", + "blockNumber": "0x13fb822", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x2b49a4b46", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x0bc9bc81ad379810b36ad5cc95387112990aa67b" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728779643, + "chain": 1, + "commit": "0cbbda3" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalsProcessor-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalsProcessor-1.json new file mode 100644 index 000000000..4d0bd766b --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/WithdrawalsProcessor-1.json @@ -0,0 +1,147 @@ +{ + "transactions": [ + { + "hash": "0xadda1555a0b03fb52c931608dd265d39da60a1e26275245e155194f330db24c7", + "transactionType": "CREATE", + "contractName": "WithdrawalsProcessor", + "contractAddress": "0x97e619c4573ce55a3aa8e105e339dce00e3bf3b2", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x1205e0", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b5061001861001d565b6100cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561006d5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100cc5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610ef9806100dc5f395ff3fe608060405234801561000f575f80fd5b50600436106100a6575f3560e01c806391d148541161006e57806391d1485414610145578063a217fddf14610158578063ad7805e81461015f578063c0c53b8b14610186578063c949761814610199578063d547741f146101ac575f80fd5b806301ffc9a7146100aa578063248a9ca3146100d25780632f2ff15d146100f357806336568abe14610108578063443dc4281461011b575b5f80fd5b6100bd6100b836600461086e565b6101bf565b60405190151581526020015b60405180910390f35b6100e56100e036600461089c565b6101f5565b6040519081526020016100c9565b6101066101013660046108d7565b610215565b005b6101066101163660046108d7565b610237565b5f5461012d906001600160a01b031681565b6040516001600160a01b0390911681526020016100c9565b6100bd6101533660046108d7565b61026f565b6100e55f81565b6100e57fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe9881565b610106610194366004610905565b6102a5565b6101066101a7366004610c0e565b610456565b6101066101ba3660046108d7565b610697565b5f6001600160e01b03198216637965db0b60e01b14806101ef57506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f9081525f80516020610ea4833981519152602052604090206001015490565b61021e826101f5565b610227816106b3565b61023183836106c0565b50505050565b6001600160a01b03811633146102605760405163334bd91960e11b815260040160405180910390fd5b61026a8282610761565b505050565b5f9182525f80516020610ea4833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156102ea5750825b90505f8267ffffffffffffffff1660011480156103065750303b155b905081158015610314575080155b156103325760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561035c57845460ff60401b1916600160401b1785555b876001600160a01b0381166103845760405163d92e233d60e01b815260040160405180910390fd5b866001600160a01b0381166103ac5760405163d92e233d60e01b815260040160405180910390fd5b6103b46107da565b5f80546001600160a01b0319166001600160a01b038c161781556103d8908a6106c0565b506104037fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe98896106c0565b505050831561044c57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b7fe0d563514842a8c29151c49cd2698127f54dd344a9b2c74a42fe9be3e305fe98610480816106b3565b5f5484516040516238a78560e71b81526001600160a01b0390921691631c53c280916104b29160040190815260200190565b602060405180830381865afa1580156104cd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104f19190610caf565b6001600160a01b031663f55b304084846040518363ffffffff1660e01b815260040161051e929190610d3c565b5f604051808303815f87803b158015610535575f80fd5b505af1158015610547573d5f803e3d5ffd5b505f9250600191506105569050565b6040519080825280602002602001820160405280156105ae57816020015b61059b60405180608001604052805f81526020015f81526020015f81526020015f81525090565b8152602001906001900390816105745790505b50905084815f815181106105c4576105c4610e21565b60209081029190910101525f5460405163c108f0cb60e01b81526001600160a01b039091169063c108f0cb906105fe908490600401610e35565b5f604051808303815f87803b158015610615575f80fd5b505af1158015610627573d5f803e3d5ffd5b505050507f39e5d24452915384663624d58b879d5ac4c8f63584b740ec27548706d34ea9bb85855160405161068892919082518152602080840151908201526040808401519082015260609283015192810192909252608082015260a00190565b60405180910390a15050505050565b6106a0826101f5565b6106a9816106b3565b6102318383610761565b6106bd81336107e4565b50565b5f5f80516020610ea48339815191526106d9848461026f565b610758575f848152602082815260408083206001600160a01b03871684529091529020805460ff1916600117905561070e3390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019150506101ef565b5f9150506101ef565b5f5f80516020610ea483398151915261077a848461026f565b15610758575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a460019150506101ef565b6107e2610825565b565b6107ee828261026f565b6108215760405163e2517d3f60e01b81526001600160a01b03821660048201526024810183905260440160405180910390fd5b5050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166107e257604051631afcd79f60e31b815260040160405180910390fd5b5f6020828403121561087e575f80fd5b81356001600160e01b031981168114610895575f80fd5b9392505050565b5f602082840312156108ac575f80fd5b5035919050565b6001600160a01b03811681146106bd575f80fd5b80356108d2816108b3565b919050565b5f80604083850312156108e8575f80fd5b8235915060208301356108fa816108b3565b809150509250929050565b5f805f60608486031215610917575f80fd5b8335610922816108b3565b92506020840135610932816108b3565b91506040840135610942816108b3565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160e0810167ffffffffffffffff811182821017156109845761098461094d565b60405290565b6040516080810167ffffffffffffffff811182821017156109845761098461094d565b604051601f8201601f1916810167ffffffffffffffff811182821017156109d6576109d661094d565b604052919050565b5f67ffffffffffffffff8211156109f7576109f761094d565b5060051b60200190565b803563ffffffff811681146108d2575f80fd5b5f82601f830112610a23575f80fd5b81356020610a38610a33836109de565b6109ad565b8083825260208201915060208460051b870101935086841115610a59575f80fd5b602086015b84811015610a7e578035610a71816108b3565b8352918301918301610a5e565b509695505050505050565b5f82601f830112610a98575f80fd5b81356020610aa8610a33836109de565b8083825260208201915060208460051b870101935086841115610ac9575f80fd5b602086015b84811015610a7e5780358352918301918301610ace565b5f82601f830112610af4575f80fd5b81356020610b04610a33836109de565b82815260059290921b84018101918181019086841115610b22575f80fd5b8286015b84811015610a7e57803567ffffffffffffffff80821115610b45575f80fd5b9088019060e0828b03601f1901811315610b5d575f80fd5b610b65610961565b610b708885016108c7565b81526040610b7f8186016108c7565b898301526060610b908187016108c7565b828401526080915081860135818401525060a0610bae818701610a01565b8284015260c091508186013585811115610bc6575f80fd5b610bd48f8c838a0101610a14565b82850152505082850135925083831115610bec575f80fd5b610bfa8d8a85880101610a89565b908201528652505050918301918301610b26565b5f805f83850360c0811215610c21575f80fd5b6080811215610c2e575f80fd5b50610c3761098a565b8435815260208501356020820152604085013560408201526060850135606082015280935050608084013567ffffffffffffffff80821115610c77575f80fd5b610c8387838801610ae5565b935060a0860135915080821115610c98575f80fd5b50610ca586828701610a89565b9150509250925092565b5f60208284031215610cbf575f80fd5b8151610895816108b3565b5f815180845260208085019450602084015f5b83811015610d025781516001600160a01b031687529582019590820190600101610cdd565b509495945050505050565b5f815180845260208085019450602084015f5b83811015610d0257815187529582019590820190600101610d20565b5f6040808301604084528086518083526060925060608601915060608160051b8701016020808a015f5b84811015610e0157898403605f19018652815180516001600160a01b03908116865284820151811685870152898201511689860152878101518886015260808082015163ffffffff169086015260a08082015160e08288018190529190610dcf83890182610cca565b9250505060c08083015192508682038188015250610ded8183610d0d565b978501979550505090820190600101610d66565b505087820390880152610e148189610d0d565b9998505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b602080825282518282018190525f9190848201906040850190845b81811015610e9757610e84838551805182526020810151602083015260408101516040830152606081015160608301525050565b9284019260809290920191600101610e50565b5090969550505050505056fe02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a264697066735822122099c24f642bdfb75892d6153155fe0f1227c1c9245ddfee88a0898ec3497cd19f64736f6c63430008180033", + "nonce": "0xd4", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xe5e0e69adc4e277b6e662170d2171195eb5f11b89010fb0d95199400b19440a2", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x6d052cded3f64aea51f6051f33b68b42016c5fba", + "function": null, + "arguments": [ + "0x97e619c4573ce55A3Aa8e105E339dce00e3bF3B2", + "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", + "0x" + ], + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610300000000000000000000000097e619c4573ce55a3aa8e105e339dce00e3bf3b2000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d697500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0xd5", + "chainId": "0x1" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0xb5234121be2144975a123c4457606e6f4e5e2a6b", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + } + ], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x1584bfd", + "logs": [ + { + "address": "0x97e619c4573ce55a3aa8e105e339dce00e3bf3b2", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "transactionHash": "0xadda1555a0b03fb52c931608dd265d39da60a1e26275245e155194f330db24c7", + "transactionIndex": "0xa2", + "logIndex": "0x2d3", + "removed": false + } + ], + "logsBloom": "0x00800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000100080000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xadda1555a0b03fb52c931608dd265d39da60a1e26275245e155194f330db24c7", + "transactionIndex": "0xa2", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "gasUsed": "0xddd23", + "effectiveGasPrice": "0x2fa03514f", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x97e619c4573ce55a3aa8e105e339dce00e3bf3b2" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x16238e1", + "logs": [ + { + "address": "0x6d052cded3f64aea51f6051f33b68b42016c5fba", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000097e619c4573ce55a3aa8e105e339dce00e3bf3b2" + ], + "data": "0x", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "transactionHash": "0xe5e0e69adc4e277b6e662170d2171195eb5f11b89010fb0d95199400b19440a2", + "transactionIndex": "0xa3", + "logIndex": "0x2d4", + "removed": false + }, + { + "address": "0xb5234121be2144975a123c4457606e6f4e5e2a6b", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + ], + "data": "0x", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "transactionHash": "0xe5e0e69adc4e277b6e662170d2171195eb5f11b89010fb0d95199400b19440a2", + "transactionIndex": "0xa3", + "logIndex": "0x2d5", + "removed": false + }, + { + "address": "0x6d052cded3f64aea51f6051f33b68b42016c5fba", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5234121be2144975a123c4457606e6f4e5e2a6b", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "transactionHash": "0xe5e0e69adc4e277b6e662170d2171195eb5f11b89010fb0d95199400b19440a2", + "transactionIndex": "0xa3", + "logIndex": "0x2d6", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000400000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000800000800000000000000002000001000000000008000000000000000840000000020000000000000001000800000000800000000000000000000000400000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000010000000000008000000000000000000000000020000000000000000000000000004000000400000000000000000020000000020000001000000000000000000000000000000000000000000000800000", + "type": "0x2", + "transactionHash": "0xe5e0e69adc4e277b6e662170d2171195eb5f11b89010fb0d95199400b19440a2", + "transactionIndex": "0xa3", + "blockHash": "0x0d6ecb0de44a610b8d97367603bd789248771887ee9702927dd8041a23c6094d", + "blockNumber": "0x13fb838", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x2fa03514f", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x6d052cded3f64aea51f6051f33b68b42016c5fba" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728779925, + "chain": 1, + "commit": "710cafc" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETH-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETH-1.json new file mode 100644 index 000000000..dee0bd42e --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETH-1.json @@ -0,0 +1,60 @@ +{ + "transactions": [ + { + "hash": "0x1139d1d65c7bc242b5e9d97cc3e37a67becd04548f42048be7a46f3887f8ca65", + "transactionType": "CREATE", + "contractName": "ynETH", + "contractAddress": "0x87e2a51d3b88fc2f5917a7ab793ea595b243710a", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x268aae", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b5061001861001d565b6100cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561006d5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100cc5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b6121a880620000dd5f395ff3fe608060405260043610610236575f3560e01c806361fd0ae111610129578063a9059cbb116100a8578063e63ab1e91161006d578063e63ab1e91461068e578063e7b0504b146106c1578063ef8b30f714610631578063f14210a6146106d6578063fb1bb9de146106f5575f80fd5b8063a9059cbb146105f3578063b7b199c714610612578063c6e6f59214610631578063d547741f14610650578063dd62ed3e1461066f575f80fd5b80638ffbf6db116100ee5780638ffbf6db1461054b57806391d148541461056a57806395d89b4114610589578063a1a25e6e1461059d578063a217fddf146105e0575f80fd5b806361fd0ae1146104bc578063631cbe3c146104db57806363d8882a146104e357806370a08231146104f75780638936a91f14610537575f80fd5b8063282c51f3116101b55780633f2a55401161017a5780633f2a55401461042857806342966c681461045f578063443dc4281461047e5780634cdad506146102e557806360da3e831461049c575f80fd5b8063282c51f3146103895780632d2da806146103bc5780632f2ff15d146103cf578063313ce567146103ee57806336568abe14610409575f80fd5b8063095ea7b3116101fb578063095ea7b31461030457806318160ddd146103235780631ef2aac51461034357806323b872dd1461034b578063248a9ca31461036a575f80fd5b806301e1d1141461025857806301ffc9a71461027f57806302191980146102ae57806306fdde03146102c457806307a2d13a146102e5575f80fd5b3661025457604051636c897afd60e01b815260040160405180910390fd5b5f80fd5b348015610263575f80fd5b5061026c610715565b6040519081526020015b60405180910390f35b34801561028a575f80fd5b5061029e610299366004611b60565b610740565b6040519015158152602001610276565b3480156102b9575f80fd5b506102c2610774565b005b3480156102cf575f80fd5b506102d86107f8565b6040516102769190611b87565b3480156102f0575f80fd5b5061026c6102ff366004611bd3565b6108b8565b34801561030f575f80fd5b5061029e61031e366004611bfe565b6108c3565b34801561032e575f80fd5b505f805160206121138339815191525461026c565b6102c26108da565b348015610356575f80fd5b5061029e610365366004611c28565b6109ef565b348015610375575f80fd5b5061026c610384366004611bd3565b610a14565b348015610394575f80fd5b5061026c7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84881565b61026c6103ca366004611c66565b610a34565b3480156103da575f80fd5b506102c26103e9366004611c81565b610b23565b3480156103f9575f80fd5b5060405160128152602001610276565b348015610414575f80fd5b506102c2610423366004611c81565b610b45565b348015610433575f80fd5b50600154610447906001600160a01b031681565b6040516001600160a01b039091168152602001610276565b34801561046a575f80fd5b506102c2610479366004611bd3565b610b7d565b348015610489575f80fd5b505f54610447906001600160a01b031681565b3480156104a7575f80fd5b5060015461029e90600160a01b900460ff1681565b3480156104c7575f80fd5b506102c26104d6366004611d90565b610bb5565b6102c2610bd6565b3480156104ee575f80fd5b506102c2610c53565b348015610502575f80fd5b5061026c610511366004611c66565b6001600160a01b03165f9081525f805160206120f3833981519152602052604090205490565b348015610542575f80fd5b506102c2610cb7565b348015610556575f80fd5b506102c2610565366004611d90565b610d32565b348015610575575f80fd5b5061029e610584366004611c81565b610d54565b348015610594575f80fd5b506102d8610d8a565b3480156105a8575f80fd5b5061029e6105b7366004611c66565b6001600160a01b03165f9081525f805160206120d3833981519152602052604090205460ff1690565b3480156105eb575f80fd5b5061026c5f81565b3480156105fe575f80fd5b5061029e61060d366004611bfe565b610dc8565b34801561061d575f80fd5b506102c261062c366004611dd2565b610dd5565b34801561063c575f80fd5b5061026c61064b366004611bd3565b6110cd565b34801561065b575f80fd5b506102c261066a366004611c81565b6110d8565b34801561067a575f80fd5b5061026c610689366004611e95565b6110f4565b348015610699575f80fd5b5061026c7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b3480156106cc575f80fd5b5061026c60025481565b3480156106e1575f80fd5b506102c26106f0366004611bd3565b61113d565b348015610700575f80fd5b5061026c5f8051602061213383398151915281565b6002545f9081906107269082611ed5565b9050610730611264565b61073a9082611ed5565b92915050565b5f6001600160e01b03198216637965db0b60e01b148061073a57506301ffc9a760e01b6001600160e01b031983161461073a565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a61079e816112dd565b6001805460ff60a01b1916600160a01b908117918290556040517f35e03641f0c63f166bca10b4ee931a3c02be26ffb29a395ce012099d99ecb6cc926107ed92900460ff161515815260200190565b60405180910390a150565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0380546060915f805160206120f38339815191529161083690611ee8565b80601f016020809104026020016040519081016040528092919081815260200182805461086290611ee8565b80156108ad5780601f10610884576101008083540402835291602001916108ad565b820191905f5260205f20905b81548152906001019060200180831161089057829003601f168201915b505050505091505090565b5f61073a825f6112ea565b5f336108d081858561132e565b5060019392505050565b5f546001600160a01b031633148061097557505f8054906101000a90046001600160a01b03166001600160a01b0316637d1f8a816040518163ffffffff1660e01b81526004016020604051808303815f875af115801561093c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109609190611f20565b6001600160a01b0316336001600160a01b0316145b61099957604051631bb2e8ef60e21b81523360048201526024015b60405180910390fd5b3460025f8282546109aa9190611ed5565b90915550506002546040805134815260208101929092527f7be0d7fd798145f666ec52ac3f0d58a38be2081e9debf17f96e11c21c848ea2791015b60405180910390a1565b5f336109fc85828561133b565b610a07858585611398565b60019150505b9392505050565b5f9081525f80516020612153833981519152602052604090206001015490565b6001545f90600160a01b900460ff1615610a61576040516313d0ff5960e31b815260040160405180910390fd5b345f03610a81576040516335ed7e5760e11b815260040160405180910390fd5b34610a8b816110cd565b9150815f03610aad57604051639811e0c760e01b815260040160405180910390fd5b610ab783836113f5565b8060025f828254610ac89190611ed5565b9091555050600254604080518381526020810185905280820192909252516001600160a01b0385169133917f4e2ca0515ed1aef1395f66b5303bb5d6f1bf9d61a353fa53f73f8ac9973fa9f69181900360600190a350919050565b610b2c82610a14565b610b35816112dd565b610b3f8383611429565b50505050565b6001600160a01b0381163314610b6e5760405163334bd91960e11b815260040160405180910390fd5b610b7882826114ca565b505050565b7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848610ba7816112dd565b610bb13383611543565b5050565b5f80516020612133833981519152610bcc816112dd565b610bb1825f611577565b6001546001600160a01b03163314610c035760405163df6752fd60e01b8152336004820152602401610990565b3460025f828254610c149190611ed5565b90915550506002546040805134815260208101929092527f61cb44cbea389abb97c617c7d16a62235c51f27da3406d3ec3c9ac87c0d0c82691016109e5565b5f80516020612133833981519152610c6a816112dd565b6001805460ff60a01b1916908190556040517f35e03641f0c63f166bca10b4ee931a3c02be26ffb29a395ce012099d99ecb6cc916107ed91600160a01b90910460ff161515815260200190565b5f80516020612133833981519152610cce816112dd565b7f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201805460ff191690556040515f805160206120d3833981519152907f5b1b646968864ea75fad546d8c819e0de710df0bf61f658f6ba54dc587472e7b905f90a15050565b5f80516020612133833981519152610d49816112dd565b610bb1826001611577565b5f9182525f80516020612153833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f805160206120f38339815191529161083690611ee8565b5f336108d0818585611398565b80516001600160a01b038116610dfe5760405163d92e233d60e01b815260040160405180910390fd5b60208201516001600160a01b038116610e2a5760405163d92e233d60e01b815260040160405180910390fd5b60408301516001600160a01b038116610e565760405163d92e233d60e01b815260040160405180910390fd5b60608401516001600160a01b038116610e825760405163d92e233d60e01b815260040160405180910390fd5b60808501516001600160a01b038116610eae5760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f81158015610ef35750825b90505f8267ffffffffffffffff166001148015610f0f5750303b155b905081158015610f1d575080155b15610f3b5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610f6557845460ff60401b1916600160401b1785555b610f6d611612565b610fb1604051806040016040528060058152602001640f2dc8aa8960db1b815250604051806040016040528060058152602001640f2dc8aa8960db1b81525061161c565b8a51610fbe905f90611429565b50610fed7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8c60200151611429565b506110095f805160206121338339815191528c60400151611429565b5060608b01515f80546001600160a01b039283166001600160a01b03199182161790915560808d0151600180549190931691161781557f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201805460ff1916909117905561107a8b60a001516001611577565b83156110c057845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050505050565b5f61073a825f61162e565b6110e182610a14565b6110ea816112dd565b610b3f83836114ca565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b5f546001600160a01b0316331461117b575f5460405163d95eb40f60e01b81526001600160a01b039091166004820152336024820152604401610990565b6002548181101561119f57604051631e9acf1760e31b815260040160405180910390fd5b5f6111aa8383611f3b565b60028190555f805460405192935090916001600160a01b039091169085908381818185875af1925050503d805f81146111fe576040519150601f19603f3d011682016040523d82523d5f602084013e611203565b606091505b5050905080611225576040516312171d8360e31b815260040160405180910390fd5b60408051858152602081018490527f72e1ec2b6bc1315b9dd8b8a5704a77cbac7378d6e44866f92d6985f0793dc4c2910160405180910390a150505050565b5f805f9054906101000a90046001600160a01b03166001600160a01b031663ff50abdc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112b4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d89190611f4e565b905090565b6112e78133611678565b50565b5f806113015f805160206121138339815191525490565b9050805f03611313578391505061073a565b6113268461131f610715565b83866116b1565b949350505050565b610b7883838360016116fc565b5f61134684846110f4565b90505f198114610b3f578181101561138a57604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610990565b610b3f84848484035f6116fc565b6001600160a01b0383166113c157604051634b637e8f60e11b81525f6004820152602401610990565b6001600160a01b0382166113ea5760405163ec442f0560e01b81525f6004820152602401610990565b610b788383836117e0565b6001600160a01b03821661141e5760405163ec442f0560e01b81525f6004820152602401610990565b610bb15f83836117e0565b5f5f805160206121538339815191526114428484610d54565b6114c1575f848152602082815260408083206001600160a01b03871684529091529020805460ff191660011790556114773390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061073a565b5f91505061073a565b5f5f805160206121538339815191526114e38484610d54565b156114c1575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061073a565b6001600160a01b03821661156c57604051634b637e8f60e11b81525f6004820152602401610990565b610bb1825f836117e0565b5f805160206120d38339815191525f5b8351811015610b3f575f8482815181106115a3576115a3611f65565b6020908102919091018101516001600160a01b0381165f818152868452604090819020805460ff19168915159081179091559051908152919350917fac6d52e87f8a8fc486e04600668caffa28c149f8286b136c0f327b4499b8c723910160405180910390a250600101611587565b61161a611889565b565b611624611889565b610bb182826118d2565b5f6116445f805160206121138339815191525490565b5f0361165157508161073a565b610a0d8361166a5f805160206121138339815191525490565b611672610715565b856116b1565b6116828282610d54565b610bb15760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610990565b5f6116de6116be836118e4565b80156116d957505f84806116d4576116d4611f79565b868809115b151590565b6116e9868686611910565b6116f39190611ed5565b95945050505050565b5f805160206120f38339815191526001600160a01b0385166117335760405163e602df0560e01b81525f6004820152602401610990565b6001600160a01b03841661175c57604051634a1406b160e11b81525f6004820152602401610990565b6001600160a01b038086165f908152600183016020908152604080832093881683529290522083905581156117d957836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040516117d091815260200190565b60405180910390a35b5050505050565b7f7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba3053201545f805160206120d38339815191529060ff16801561183857506001600160a01b0384165f9081526020829052604090205460ff16155b801561184c57506001600160a01b03841615155b801561186057506001600160a01b03831615155b1561187e57604051636ab17e0560e01b815260040160405180910390fd5b610b3f8484846119c6565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661161a57604051631afcd79f60e31b815260040160405180910390fd5b6118da611889565b610bb18282611aff565b5f60028260038111156118f9576118f9611f8d565b6119039190611fa1565b60ff166001149050919050565b5f838302815f1985870982811083820303915050805f036119445783828161193a5761193a611f79565b0492505050610a0d565b80841161195b5761195b6003851502601118611b4f565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f805160206120f38339815191526001600160a01b038416611a005781816002015f8282546119f59190611ed5565b90915550611a709050565b6001600160a01b0384165f9081526020829052604090205482811015611a525760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610990565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316611a8e576002810180548390039055611aac565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051611af191815260200190565b60405180910390a350505050565b611b07611889565b5f805160206120f38339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace03611b408482612012565b5060048101610b3f8382612012565b634e487b715f52806020526024601cfd5b5f60208284031215611b70575f80fd5b81356001600160e01b031981168114610a0d575f80fd5b5f602080835283518060208501525f5b81811015611bb357858101830151858201604001528201611b97565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215611be3575f80fd5b5035919050565b6001600160a01b03811681146112e7575f80fd5b5f8060408385031215611c0f575f80fd5b8235611c1a81611bea565b946020939093013593505050565b5f805f60608486031215611c3a575f80fd5b8335611c4581611bea565b92506020840135611c5581611bea565b929592945050506040919091013590565b5f60208284031215611c76575f80fd5b8135610a0d81611bea565b5f8060408385031215611c92575f80fd5b823591506020830135611ca481611bea565b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715611ce657611ce6611caf565b60405290565b5f82601f830112611cfb575f80fd5b8135602067ffffffffffffffff80831115611d1857611d18611caf565b8260051b604051601f19603f83011681018181108482111715611d3d57611d3d611caf565b6040529384526020818701810194908101925087851115611d5c575f80fd5b6020870191505b84821015611d85578135611d7681611bea565b83529183019190830190611d63565b979650505050505050565b5f60208284031215611da0575f80fd5b813567ffffffffffffffff811115611db6575f80fd5b61132684828501611cec565b8035611dcd81611bea565b919050565b5f60208284031215611de2575f80fd5b813567ffffffffffffffff80821115611df9575f80fd5b9083019060c08286031215611e0c575f80fd5b611e14611cc3565b8235611e1f81611bea565b81526020830135611e2f81611bea565b60208201526040830135611e4281611bea565b6040820152611e5360608401611dc2565b6060820152611e6460808401611dc2565b608082015260a083013582811115611e7a575f80fd5b611e8687828601611cec565b60a08301525095945050505050565b5f8060408385031215611ea6575f80fd5b8235611eb181611bea565b91506020830135611ca481611bea565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561073a5761073a611ec1565b600181811c90821680611efc57607f821691505b602082108103611f1a57634e487b7160e01b5f52602260045260245ffd5b50919050565b5f60208284031215611f30575f80fd5b8151610a0d81611bea565b8181038181111561073a5761073a611ec1565b5f60208284031215611f5e575f80fd5b5051919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b5f60ff831680611fbf57634e487b7160e01b5f52601260045260245ffd5b8060ff84160691505092915050565b601f821115610b7857805f5260205f20601f840160051c81016020851015611ff35750805b601f840160051c820191505b818110156117d9575f8155600101611fff565b815167ffffffffffffffff81111561202c5761202c611caf565b6120408161203a8454611ee8565b84611fce565b602080601f831160018114612073575f841561205c5750858301515b5f19600386901b1c1916600185901b1785556120ca565b5f85815260208120601f198616915b828110156120a157888601518255948401946001909101908401612082565b50858210156120be57878501515f19600388901b60f8161c191681555b505060018460011b0185555b50505050505056fe7e7ba5b20f89141f0255e9704ce6ce6e55f5f28e4fc0d626fc76bedba305320052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a264697066735822122034c087a61bb674f4958a98f5cf1bd52d2f584c4c96bab73fa37add66d2ce63c364736f6c63430008180033", + "nonce": "0xce", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x4956a9", + "logs": [ + { + "address": "0x87e2a51d3b88fc2f5917a7ab793ea595b243710a", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0x74c24deec927b6a05c84fac49312cedbd8763294a2047c5e997e3c3a536f9474", + "blockNumber": "0x13fb282", + "transactionHash": "0x1139d1d65c7bc242b5e9d97cc3e37a67becd04548f42048be7a46f3887f8ca65", + "transactionIndex": "0xe", + "logIndex": "0x67", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x1139d1d65c7bc242b5e9d97cc3e37a67becd04548f42048be7a46f3887f8ca65", + "transactionIndex": "0xe", + "blockHash": "0x74c24deec927b6a05c84fac49312cedbd8763294a2047c5e997e3c3a536f9474", + "blockNumber": "0x13fb282", + "gasUsed": "0x1da5c1", + "effectiveGasPrice": "0x22c54677c", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x87e2a51d3b88fc2f5917a7ab793ea595b243710a" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728762193, + "chain": 1, + "commit": "717dcb3" +} \ No newline at end of file diff --git a/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETHRedemptionAssetsVault-1.json b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETHRedemptionAssetsVault-1.json new file mode 100644 index 000000000..562d5949f --- /dev/null +++ b/deployments/yneth-withdrawals-296a852467ad52813c45a8141b9201754bb093d7/ynETHRedemptionAssetsVault-1.json @@ -0,0 +1,133 @@ +{ + "transactions": [ + { + "hash": "0x8f177b2b948e33745ee00200bdabce5a8286656cf9414edd7efd0c699da6658a", + "transactionType": "CREATE", + "contractName": "ynETHRedemptionAssetsVault", + "contractAddress": "0xe1a2a7fac6729360e6794f2f137621e2cdb0fed5", + "function": null, + "arguments": null, + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0x117065", + "value": "0x0", + "input": "0x608060405234801561000f575f80fd5b50610eeb8061001d5f395ff3fe608060405260043610610113575f3560e01c80638456cb591161009d578063d26b3e2611610062578063d26b3e261461031d578063d2edb68f1461033c578063d547741f1461035b578063e63ab1e91461037a578063fb1bb9de146103ad575f80fd5b80638456cb59146102a757806391d14854146102bb5780639872ab84146102da578063a217fddf146102f8578063acd08aa71461030b575f80fd5b806336568abe116100e357806336568abe146102225780633f4ba83a14610241578063540385a3146102555780635c62c822146102695780635c975abb14610288575f80fd5b806301ffc9a714610169578063248a9ca31461019d5780632ba29d38146101ca5780632f2ff15d14610201575f80fd5b366101655760405134815273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9033907f34a31c07280ba0d3e5c883a4468da49d04e80d542a11e74c91fa4aa96a3c96da9060200160405180910390a3005b5f80fd5b348015610174575f80fd5b50610188610183366004610cf9565b6103e0565b60405190151581526020015b60405180910390f35b3480156101a8575f80fd5b506101bc6101b7366004610d27565b610416565b604051908152602001610194565b3480156101d5575f80fd5b506001546101e9906001600160a01b031681565b6040516001600160a01b039091168152602001610194565b34801561020c575f80fd5b5061022061021b366004610d52565b610436565b005b34801561022d575f80fd5b5061022061023c366004610d52565b610458565b34801561024c575f80fd5b50610220610490565b348015610260575f80fd5b506101bc6104c9565b348015610274575f80fd5b50610220610283366004610d27565b610540565b348015610293575f80fd5b505f5461018890600160a01b900460ff1681565b3480156102b2575f80fd5b5061022061068b565b3480156102c6575f80fd5b506101886102d5366004610d52565b6106ca565b3480156102e5575f80fd5b505f546101e9906001600160a01b031681565b348015610303575f80fd5b506101bc5f81565b348015610316575f80fd5b50476101bc565b348015610328575f80fd5b50610220610337366004610d80565b610700565b348015610347575f80fd5b50610220610356366004610dfc565b610937565b348015610366575f80fd5b50610220610375366004610d52565b610adf565b348015610385575f80fd5b506101bc7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b3480156103b8575f80fd5b506101bc7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b5f6001600160e01b03198216637965db0b60e01b148061041057506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f9081525f80516020610e96833981519152602052604090206001015490565b61043f82610416565b61044881610afb565b6104528383610b05565b50505050565b6001600160a01b03811633146104815760405163334bd91960e11b815260040160405180910390fd5b61048b8282610ba6565b505050565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a6104ba81610afb565b505f805460ff60a01b19169055565b5f805460405163266d6a8360e11b8152670de0b6b3a764000060048201526001600160a01b0390911690634cdad50690602401602060405180830381865afa158015610517573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061053b9190610e7e565b905090565b6001546001600160a01b0316331461057257604051630bde7ac360e11b81523360048201526024015b60405180910390fd5b5f54600160a01b900460ff161561059c5760405163ab35696f60e01b815260040160405180910390fd5b6105a4610c1f565b5f8054906101000a90046001600160a01b03166001600160a01b0316631ef2aac5826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156105ef575f80fd5b505af1158015610601573d5f803e3d5ffd5b50505f546040518581526001600160a01b03909116935033925073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee91507f10964f2d145c29d7c7688147486387f37f033564029b447ab7c0983f292acc789060200160405180910390a461068860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6106b581610afb565b505f805460ff60a01b1916600160a01b179055565b5f9182525f80516020610e96833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b80516001600160a01b0381166107295760405163d92e233d60e01b815260040160405180910390fd5b60208201516001600160a01b0381166107555760405163d92e233d60e01b815260040160405180910390fd5b60408301516001600160a01b0381166107815760405163d92e233d60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156107c65750825b90505f8267ffffffffffffffff1660011480156107e25750303b155b9050811580156107f0575080155b1561080e5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561083857845460ff60401b1916600160401b1785555b610840610c69565b885161084d905f90610b05565b5061087b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8a5f0151610b05565b506108a97f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a8a5f0151610b05565b506020890151600180546001600160a01b039283166001600160a01b031990911617905560408a01515f80546001600160a81b03191691909216179055831561092c57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050505050565b6001546001600160a01b0316331461096457604051630bde7ac360e11b8152336004820152602401610569565b5f54600160a01b900460ff161561098e5760405163ab35696f60e01b815260040160405180910390fd5b610996610c1f565b47838110156109dc5760405163a8c6953f60e01b815273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee60048201526024810185905260448101829052606401610569565b5f856001600160a01b0316856040515f6040518083038185875af1925050503d805f8114610a25576040519150601f19603f3d011682016040523d82523d5f602084013e610a2a565b606091505b5050905080610a5e5760405163f68179b160e01b8152600481018690526001600160a01b0387166024820152604401610569565b6040518581526001600160a01b03871690339073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee907f25137ec1c481240ec1a0e55c3c8c5f5e3467c1a66454f84fb39c963378cc0e779060200160405180910390a4505061045260017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b610ae882610416565b610af181610afb565b6104528383610ba6565b6106888133610c73565b5f5f80516020610e96833981519152610b1e84846106ca565b610b9d575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610b533390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a46001915050610410565b5f915050610410565b5f5f80516020610e96833981519152610bbf84846106ca565b15610b9d575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a46001915050610410565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805460011901610c6357604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b610c71610cb0565b565b610c7d82826106ca565b610cac5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610569565b5050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610c7157604051631afcd79f60e31b815260040160405180910390fd5b5f60208284031215610d09575f80fd5b81356001600160e01b031981168114610d20575f80fd5b9392505050565b5f60208284031215610d37575f80fd5b5035919050565b6001600160a01b0381168114610688575f80fd5b5f8060408385031215610d63575f80fd5b823591506020830135610d7581610d3e565b809150509250929050565b5f60608284031215610d90575f80fd5b6040516060810181811067ffffffffffffffff82111715610dbf57634e487b7160e01b5f52604160045260245ffd5b6040528235610dcd81610d3e565b81526020830135610ddd81610d3e565b60208201526040830135610df081610d3e565b60408201529392505050565b5f805f8060608587031215610e0f575f80fd5b8435610e1a81610d3e565b935060208501359250604085013567ffffffffffffffff80821115610e3d575f80fd5b818701915087601f830112610e50575f80fd5b813581811115610e5e575f80fd5b886020828501011115610e6f575f80fd5b95989497505060200194505050565b5f60208284031215610e8e575f80fd5b505191905056fe02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212208d27b179d865bd32c94fae6a09a9a953118f6745b55ba84f343c1cd38020a92f64736f6c63430008180033", + "nonce": "0xd0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x77ab8db3472f1e01be8783a78e5e804bfb3b917c3037c0ec3d3e66da0455f247", + "transactionType": "CREATE", + "contractName": "TransparentUpgradeableProxy", + "contractAddress": "0x5d6e53c42e3b37f82f693937bc508940769c5caf", + "function": null, + "arguments": [ + "0xe1a2A7FAc6729360e6794f2f137621E2Cdb0Fed5", + "0xfcad670592a3b24869C0b51a6c6FDED4F95D6975", + "0x" + ], + "transaction": { + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "gas": "0xce728", + "value": "0x0", + "input": "0x60a060405260405162000e5038038062000e508339810160408190526200002691620003bc565b828162000034828262000099565b50508160405162000045906200035a565b6001600160a01b039091168152602001604051809103905ff0801580156200006f573d5f803e3d5ffd5b506001600160a01b0316608052620000906200008a60805190565b620000fe565b505050620004b3565b620000a4826200016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115620000f057620000eb8282620001ee565b505050565b620000fa62000267565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200013f5f8051602062000e30833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a16200016c8162000289565b50565b806001600160a01b03163b5f03620001aa57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516200020c919062000496565b5f60405180830381855af49150503d805f811462000246576040519150601f19603f3d011682016040523d82523d5f602084013e6200024b565b606091505b5090925090506200025e858383620002ca565b95945050505050565b3415620002875760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b038116620002b457604051633173bdd160e11b81525f6004820152602401620001a1565b805f8051602062000e30833981519152620001cd565b606082620002e357620002dd8262000330565b62000329565b8151158015620002fb57506001600160a01b0384163b155b156200032657604051639996b31560e01b81526001600160a01b0385166004820152602401620001a1565b50805b9392505050565b805115620003415780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b6104fc806200093483390190565b80516001600160a01b03811681146200037f575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f5b83811015620003b45781810151838201526020016200039a565b50505f910152565b5f805f60608486031215620003cf575f80fd5b620003da8462000368565b9250620003ea6020850162000368565b60408501519092506001600160401b038082111562000407575f80fd5b818601915086601f8301126200041b575f80fd5b81518181111562000430576200043062000384565b604051601f8201601f19908116603f011681019083821181831017156200045b576200045b62000384565b8160405282815289602084870101111562000474575f80fd5b6200048783602083016020880162000398565b80955050505050509250925092565b5f8251620004a981846020870162000398565b9190910192915050565b608051610469620004cb5f395f601001526104695ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82515f5b81811015610426576020818601810151858301520161040c565b505f92019182525091905056fea264697066735822122055a96d9f6d94a6ea50b308b2fc4aa0566df833e7551386bf651c4f24b0cc8f0a64736f6c63430008180033608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103000000000000000000000000e1a2a7fac6729360e6794f2f137621e2cdb0fed5000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d697500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0xd1", + "chainId": "0x1" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0x364b8154ea1673e360446c3d17480d32c3e71b2c", + "initCode": "0x608060405234801561000f575f80fd5b506040516104fc3803806104fc83398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b610407806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100859190610372565b3480156100e9575f80fd5b506100616100f836600461038b565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061014890869086906004016103a6565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084525f5b8181101561035357602081850181015186830182015201610337565b505f602082860101526020601f19601f83011685010191505092915050565b602081525f610384602083018461032f565b9392505050565b5f6020828403121561039b575f80fd5b813561038481610238565b6001600160a01b03831681526040602082018190525f906103c99083018461032f565b94935050505056fea2646970667358221220adbbc4425922655b0b518570c1f1ea2803ed1402d9e8c09bdf19a05b4208da4664736f6c63430008180033000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + } + ], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x9fc117", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x8f177b2b948e33745ee00200bdabce5a8286656cf9414edd7efd0c699da6658a", + "transactionIndex": "0x74", + "blockHash": "0x90be87214fc580cbb09757be9071a9675a703f54b0a94e0e1abac0562b45fa12", + "blockNumber": "0x13fb7c6", + "gasUsed": "0xd6a27", + "effectiveGasPrice": "0x26883dc02", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0xe1a2a7fac6729360e6794f2f137621e2cdb0fed5" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xa9adfb", + "logs": [ + { + "address": "0x5d6e53c42e3b37f82f693937bc508940769c5caf", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000e1a2a7fac6729360e6794f2f137621e2cdb0fed5" + ], + "data": "0x", + "blockHash": "0x90be87214fc580cbb09757be9071a9675a703f54b0a94e0e1abac0562b45fa12", + "blockNumber": "0x13fb7c6", + "transactionHash": "0x77ab8db3472f1e01be8783a78e5e804bfb3b917c3037c0ec3d3e66da0455f247", + "transactionIndex": "0x75", + "logIndex": "0x10d", + "removed": false + }, + { + "address": "0x364b8154ea1673e360446c3d17480d32c3e71b2c", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000fcad670592a3b24869c0b51a6c6fded4f95d6975" + ], + "data": "0x", + "blockHash": "0x90be87214fc580cbb09757be9071a9675a703f54b0a94e0e1abac0562b45fa12", + "blockNumber": "0x13fb7c6", + "transactionHash": "0x77ab8db3472f1e01be8783a78e5e804bfb3b917c3037c0ec3d3e66da0455f247", + "transactionIndex": "0x75", + "logIndex": "0x10e", + "removed": false + }, + { + "address": "0x5d6e53c42e3b37f82f693937bc508940769c5caf", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000364b8154ea1673e360446c3d17480d32c3e71b2c", + "blockHash": "0x90be87214fc580cbb09757be9071a9675a703f54b0a94e0e1abac0562b45fa12", + "blockNumber": "0x13fb7c6", + "transactionHash": "0x77ab8db3472f1e01be8783a78e5e804bfb3b917c3037c0ec3d3e66da0455f247", + "transactionIndex": "0x75", + "logIndex": "0x10f", + "removed": false + } + ], + "logsBloom": "0x00000000000002000000000000000000400000000000000000800000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000002000001000000000008000000000000000800000000020000000010200000000800000000800000000000000000000000400000000000010000000000000000000000000000010000000000000010800000000000000000000000000000000000000000000000000000000000020000000000000020000000000000000000000000000000000400000000000000000020000000020000800000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x77ab8db3472f1e01be8783a78e5e804bfb3b917c3037c0ec3d3e66da0455f247", + "transactionIndex": "0x75", + "blockHash": "0x90be87214fc580cbb09757be9071a9675a703f54b0a94e0e1abac0562b45fa12", + "blockNumber": "0x13fb7c6", + "gasUsed": "0x9ece4", + "effectiveGasPrice": "0x26883dc02", + "from": "0xa1e340bd1e3ea09b3981164bbb4afeddf0e7ba0d", + "to": null, + "contractAddress": "0x5d6e53c42e3b37f82f693937bc508940769c5caf" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1728778542, + "chain": 1, + "commit": "65893bb" +} \ No newline at end of file diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index 90a0f6aee..bda003385 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit 90a0f6aee79b4a38e1b63b32f9627f21b1162fbb +Subproject commit bda003385c5fec59e35196dc14d01f17d1eb7001 diff --git a/lib/forge-std b/lib/forge-std index 58d305198..2f1126975 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 58d30519826c313ce47345abedfdc07679e944d1 +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dbb6104ce..1edc2ae00 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit 1edc2ae004974ebf053f4eba26b45469937b9381 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 723f8cab0..22489db15 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 +Subproject commit 22489db15621b9a42ebddb1facade6962034e9b9 diff --git a/script/Actors.sol b/script/Actors.sol index 7ab83f22f..bfc69facf 100644 --- a/script/Actors.sol +++ b/script/Actors.sol @@ -27,6 +27,10 @@ contract ActorAddresses { address POOLED_DEPOSITS_OWNER; address PAUSE_ADMIN; address REFERRAL_PUBLISHER; + address WITHDRAWAL_MANAGER; + address REDEMPTION_ASSET_WITHDRAWER; + address REQUEST_FINALIZER; + address STAKING_NODES_WITHDRAWER; address STRATEGY_CONTROLLER; address TOKEN_STAKING_NODE_OPERATOR; } @@ -38,6 +42,8 @@ contract ActorAddresses { address YNValidatorService; address YNStrategyController; address YNTokenStakingNodeOperator; + address YNWithdrawalsETH; + address YnOperator; } struct Actors { @@ -57,12 +63,12 @@ contract ActorAddresses { YNDelegator: 0x743b91CDB1C694D4F51bCDA3a4A59DcC0d02b913, YNDev: 0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39, YNValidatorService: 0x9Dd8F69b62ddFd990241530F47dcEd0Dad7f7d39, + YNStrategyController: 0x447F34933D3Eeac79a8E22352BaC976A1701aee0, // TODO: replace with concrete deployment - YNStrategyController: 0x1234567890123456789012345678901234567890, - // TODO: replace with concrete deployment - YNTokenStakingNodeOperator: 0x2234567890123456789012345678901234567890 + YNTokenStakingNodeOperator: 0x2234567890123456789012345678901234567890, + YNWithdrawalsETH: 0x0e36E2bCD71059E02822DFE52cBa900730b07c07, + YnOperator: 0x530F6057e93b54Ec39D6472DA75712db2178780C }); - actors[17000] = Actors({ eoa: EOAActors({ DEFAULT_SIGNER: 0x72fdBD51085bDa5eEEd3b55D1a46E2e92f0837a5, @@ -82,14 +88,18 @@ contract ActorAddresses { EIGEN_STRATEGY_ADMIN: holeskyWallets.YNSecurityCouncil }), ops: OpsActors({ - STAKING_NODES_OPERATOR: holeskyWallets.YNDev, + STAKING_NODES_OPERATOR: holeskyWallets.YnOperator, VALIDATOR_MANAGER: holeskyWallets.YNValidatorService, STAKING_NODE_CREATOR: holeskyWallets.YNDev, POOLED_DEPOSITS_OWNER: holeskyWallets.YNDev, PAUSE_ADMIN: holeskyWallets.YNSecurityCouncil, REFERRAL_PUBLISHER: holeskyWallets.YNDev, STRATEGY_CONTROLLER: holeskyWallets.YNStrategyController, - TOKEN_STAKING_NODE_OPERATOR: holeskyWallets.YNTokenStakingNodeOperator + TOKEN_STAKING_NODE_OPERATOR: holeskyWallets.YNTokenStakingNodeOperator, + WITHDRAWAL_MANAGER: holeskyWallets.YNWithdrawalsETH, + REDEMPTION_ASSET_WITHDRAWER: holeskyWallets.YNDev, + REQUEST_FINALIZER: holeskyWallets.YNWithdrawalsETH, + STAKING_NODES_WITHDRAWER: holeskyWallets.YNWithdrawalsETH }), wallets: holeskyWallets }); @@ -100,10 +110,11 @@ contract ActorAddresses { YNDelegator: 0xDF51B7843817F76220C0970eF58Ba726630028eF, YNDev: 0xa08F39d30dc865CC11a49b6e5cBd27630D6141C3, YNValidatorService: 0x8e20eAf121154B69B7b880FA6c617c0175c4dE2e, - // TODO: replace with concrete deployment YNStrategyController: 0x0573A7DaFBc080064663623979287286Bb65C1BD, // TODO: replace with concrete deployment - YNTokenStakingNodeOperator: 0xfcad670592a3b24869C0b51a6c6FDED4F95D6975 // same as YNSecurityCouncil + YNTokenStakingNodeOperator: 0xfcad670592a3b24869C0b51a6c6FDED4F95D6975, // same as YNSecurityCouncil + YNWithdrawalsETH: 0x7f7187fbD6e508bC23268746dff535cfC8EbC87b, + YnOperator: 0x591A163AcfDb6F79674b08e5F069b4905a230ddD }); actors[1] = Actors({ @@ -125,14 +136,18 @@ contract ActorAddresses { EIGEN_STRATEGY_ADMIN: mainnetWallets.YNSecurityCouncil }), ops: OpsActors({ - STAKING_NODES_OPERATOR:mainnetWallets.YNDev, + STAKING_NODES_OPERATOR:mainnetWallets.YnOperator, VALIDATOR_MANAGER: mainnetWallets.YNValidatorService, STAKING_NODE_CREATOR: mainnetWallets.YNDev, POOLED_DEPOSITS_OWNER: 0xE1fAc59031520FD1eb901da990Da12Af295e6731, PAUSE_ADMIN: mainnetWallets.YNDev, REFERRAL_PUBLISHER: mainnetWallets.YNDev, STRATEGY_CONTROLLER: mainnetWallets.YNStrategyController, - TOKEN_STAKING_NODE_OPERATOR: mainnetWallets.YNTokenStakingNodeOperator + TOKEN_STAKING_NODE_OPERATOR: mainnetWallets.YNTokenStakingNodeOperator, + WITHDRAWAL_MANAGER: mainnetWallets.YNWithdrawalsETH, + REDEMPTION_ASSET_WITHDRAWER: mainnetWallets.YNDev, + REQUEST_FINALIZER: mainnetWallets.YNWithdrawalsETH, + STAKING_NODES_WITHDRAWER: mainnetWallets.YNWithdrawalsETH }), wallets: mainnetWallets }); diff --git a/script/BaseScript.s.sol b/script/BaseScript.s.sol index 23e86c9a1..f74c5afa0 100644 --- a/script/BaseScript.s.sol +++ b/script/BaseScript.s.sol @@ -11,11 +11,20 @@ import {Script} from "lib/forge-std/src/Script.sol"; import {Utils} from "script/Utils.sol"; import {ActorAddresses} from "script/Actors.sol"; import {ContractAddresses} from "script/ContractAddresses.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; import {console} from "lib/forge-std/src/console.sol"; + abstract contract BaseScript is Script, Utils { using stdJson for string; + struct ProxyAddresses { + TransparentUpgradeableProxy proxy; + ProxyAdmin proxyAdmin; + address implementation; + } + ActorAddresses private _actorAddresses = new ActorAddresses(); ContractAddresses private _contractAddresses = new ContractAddresses(); @@ -45,6 +54,14 @@ abstract contract BaseScript is Script, Utils { vm.serializeAddress(json, string.concat("implementation-", name), implementation); } + function loadProxyAddresses(string memory jsonContent, string memory contractName) internal pure returns (ProxyAddresses memory) { + ProxyAddresses memory proxyAddresses; + proxyAddresses.proxy = TransparentUpgradeableProxy(payable(jsonContent.readAddress(string.concat(".proxy-", contractName)))); + proxyAddresses.proxyAdmin = ProxyAdmin(jsonContent.readAddress(string.concat(".proxyAdmin-", contractName))); + proxyAddresses.implementation = jsonContent.readAddress(string.concat(".implementation-", contractName)); + return proxyAddresses; + } + function getActors() public returns (ActorAddresses.Actors memory actors) { return _actorAddresses.getActors(block.chainid); } diff --git a/script/ContractAddresses.sol b/script/ContractAddresses.sol index 4a1d21ea7..ebc73cf06 100644 --- a/script/ContractAddresses.sol +++ b/script/ContractAddresses.sol @@ -20,6 +20,9 @@ contract ContractAddresses { address REWARDS_DISTRIBUTOR_ADDRESS; address EXECUTION_LAYER_RECEIVER_ADDRESS; address CONSENSUS_LAYER_RECEIVER_ADDRESS; + address YNETH_REDEMPTION_ASSETS_VAULT_ADDRESS; + address WITHDRAWAL_QUEUE_MANAGER_ADDRESS; + address WITHDRAWALS_PROCESSOR_ADDRESS; } struct EigenlayerAddresses { @@ -119,7 +122,10 @@ contract ContractAddresses { STAKING_NODES_MANAGER_ADDRESS: 0x8C33A1d6d062dB7b51f79702355771d44359cD7d, REWARDS_DISTRIBUTOR_ADDRESS: 0x40d5FF3E218f54f4982661a0464a298Cf6652351, EXECUTION_LAYER_RECEIVER_ADDRESS: 0x1D6b2a11FFEa5F9a8Ed85A02581910b3d695C12b, - CONSENSUS_LAYER_RECEIVER_ADDRESS: 0xE439fe4563F7666FCd7405BEC24aE7B0d226536e + CONSENSUS_LAYER_RECEIVER_ADDRESS: 0xE439fe4563F7666FCd7405BEC24aE7B0d226536e, + YNETH_REDEMPTION_ASSETS_VAULT_ADDRESS: 0x5D6e53c42E3B37f82F693937BC508940769c5caf, + WITHDRAWAL_QUEUE_MANAGER_ADDRESS: 0x0BC9BC81aD379810B36AD5cC95387112990AA67b, + WITHDRAWALS_PROCESSOR_ADDRESS: 0x6d052CdEd3F64aea51f6051F33b68b42016C5FbA }), ynEigen: YnEigenAddresses({ YNEIGEN_ADDRESS: 0x35Ec69A77B79c255e5d47D5A3BdbEFEfE342630c, @@ -174,7 +180,10 @@ contract ContractAddresses { STAKING_NODES_MANAGER_ADDRESS: 0xc2387EBb4Ea66627E3543a771e260Bd84218d6a1, REWARDS_DISTRIBUTOR_ADDRESS: 0x82915efF62af9FCC0d0735b8681959e069E3f2D8, EXECUTION_LAYER_RECEIVER_ADDRESS: 0xA5E9E1ceb4cC1854d0e186a9B3E67158b84AD072, - CONSENSUS_LAYER_RECEIVER_ADDRESS: 0x706EED02702fFE9CBefD6A65E63f3C2b59B7eF2d + CONSENSUS_LAYER_RECEIVER_ADDRESS: 0x706EED02702fFE9CBefD6A65E63f3C2b59B7eF2d, + YNETH_REDEMPTION_ASSETS_VAULT_ADDRESS: 0x3a2DD2f0f5A20768110a52fC4f091AB9d8631b58, + WITHDRAWAL_QUEUE_MANAGER_ADDRESS: 0x141aAb320857145fB42240C979b800f48CE5B678, + WITHDRAWALS_PROCESSOR_ADDRESS: 0x48E3FdCE3E2d5A3Fa34bdEd9eb9dEeBB48217ba3 }), ynEigen: YnEigenAddresses({ YNEIGEN_ADDRESS: 0x071bdC8eDcdD66730f45a3D3A6F794FAA37C75ED, diff --git a/script/Utils.sol b/script/Utils.sol index e66a44a0d..017107d86 100644 --- a/script/Utils.sol +++ b/script/Utils.sol @@ -60,4 +60,18 @@ contract Utils { return difference <= 2; // Allow for a 1-2 wei difference only } } + + /** + * @dev Creates an array of uint256 with a specified value repeated a given number of times. + * @param value The value to be repeated in the array. + * @param count The number of times to repeat the value. + * @return uint256[] An array containing the repeated value. + */ + function repeat(uint256 value, uint256 count) public pure returns (uint256[] memory) { + uint256[] memory result = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + result[i] = value; + } + return result; + } } \ No newline at end of file diff --git a/script/VerifyYnETH.s.sol b/script/VerifyYnETH.s.sol deleted file mode 100644 index 1ecf42a18..000000000 --- a/script/VerifyYnETH.s.sol +++ /dev/null @@ -1,411 +0,0 @@ -/// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; - -import {ContractAddresses} from "script/ContractAddresses.sol"; -import {BaseYnETHScript} from "script/BaseYnETHScript.s.sol"; -import { IEigenPodManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; -import {IStakingNode} from "src/interfaces/IStakingNode.sol"; -import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {Utils} from "script/Utils.sol"; - -import {ActorAddresses} from "script/Actors.sol"; -import {console} from "lib/forge-std/src/console.sol"; - -contract Verify is BaseYnETHScript { - - Deployment deployment; - ActorAddresses.Actors actors; - ContractAddresses.ChainAddresses chainAddresses; - - function run() external { - - ContractAddresses contractAddresses = new ContractAddresses(); - chainAddresses = contractAddresses.getChainAddresses(block.chainid); - - deployment = loadDeployment(); - actors = getActors(); - - verifyProxyAdminOwners(); - verifyRoles(); - verifySystemParameters(); - verifyContractDependencies(); - } - - function verifyProxyAdminOwners() internal view { - address ynETHAdmin = ProxyAdmin(Utils.getTransparentUpgradeableProxyAdminAddress(address(deployment.ynETH))).owner(); - require( - ynETHAdmin == actors.admin.PROXY_ADMIN_OWNER, - string.concat("ynETH: PROXY_ADMIN_OWNER INVALID, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(ynETHAdmin)) - ); - console.log("\u2705 ynETH: PROXY_ADMIN_OWNER - ", vm.toString(ynETHAdmin)); - - address rewardsDistributorAdmin = ProxyAdmin(Utils.getTransparentUpgradeableProxyAdminAddress(address(deployment.rewardsDistributor))).owner(); - require( - rewardsDistributorAdmin == actors.admin.PROXY_ADMIN_OWNER, - string.concat("rewardsDistributor: PROXY_ADMIN_OWNER INVALID, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(rewardsDistributorAdmin)) - ); - console.log("\u2705 rewardsDistributor: PROXY_ADMIN_OWNER - ", vm.toString(rewardsDistributorAdmin)); - - address stakingNodesManagerAdmin = ProxyAdmin(Utils.getTransparentUpgradeableProxyAdminAddress(address(deployment.stakingNodesManager))).owner(); - require( - stakingNodesManagerAdmin == actors.admin.PROXY_ADMIN_OWNER, - string.concat("stakingNodesManager: PROXY_ADMIN_OWNER INVALID, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(stakingNodesManagerAdmin)) - ); - console.log("\u2705 stakingNodesManager: PROXY_ADMIN_OWNER - ", vm.toString(stakingNodesManagerAdmin)); - - address consensusLayerReceiverAdmin = ProxyAdmin(Utils.getTransparentUpgradeableProxyAdminAddress(address(deployment.consensusLayerReceiver))).owner(); - require( - consensusLayerReceiverAdmin == actors.admin.PROXY_ADMIN_OWNER, - string.concat("consensusLayerReceiver: PROXY_ADMIN_OWNER INVALID, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(consensusLayerReceiverAdmin)) - ); - console.log("\u2705 consensusLayerReceiver: PROXY_ADMIN_OWNER - ", vm.toString(consensusLayerReceiverAdmin)); - - address executionLayerReceiverAdmin = ProxyAdmin(Utils.getTransparentUpgradeableProxyAdminAddress(address(deployment.executionLayerReceiver))).owner(); - require( - executionLayerReceiverAdmin == actors.admin.PROXY_ADMIN_OWNER, - string.concat("executionLayerReceiver: PROXY_ADMIN_OWNER INVALID, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(executionLayerReceiverAdmin)) - ); - console.log("\u2705 executionLayerReceiver: PROXY_ADMIN_OWNER - ", vm.toString(executionLayerReceiverAdmin)); - } - - function verifyRoles() internal view { - - //-------------------------------------------------------------------------------------- - //---------------- consesusLayerReceiver roles --------------------------------------- - //-------------------------------------------------------------------------------------- - // WITHDRAWER_ROLE - require( - deployment.consensusLayerReceiver.hasRole( - deployment.consensusLayerReceiver.WITHDRAWER_ROLE(), - address(deployment.rewardsDistributor) - ), - "consensusLayerReceiver: WITHDRAWER_ROLE INVALID" - ); - console.log("\u2705 consensusLayerReceiver: WITHDRAWER_ROLE - ", vm.toString(address(deployment.rewardsDistributor))); - - // DEFAULT_ADMIN_ROLE - require( - deployment.consensusLayerReceiver.hasRole( - deployment.consensusLayerReceiver.DEFAULT_ADMIN_ROLE(), - address(actors.admin.ADMIN) - ), - "consensusLayerReceiver: DEFAULT_ADMIN_ROLE INVALID" - ); - console.log("\u2705 consensusLayerReceiver: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); - - - //-------------------------------------------------------------------------------------- - //--------------- executionLayerReceiver roles --------------------------------------- - //-------------------------------------------------------------------------------------- - // WITHDRAWER_ROLE - require( - deployment.executionLayerReceiver.hasRole( - deployment.executionLayerReceiver.WITHDRAWER_ROLE(), - address(deployment.rewardsDistributor) - ), - "executionLayerReceiver: WITHDRAWER_ROLE INVALID" - ); - console.log("\u2705 executionLayerReceiver: WITHDRAWER_ROLE - ", vm.toString(address(deployment.rewardsDistributor))); - - // DEFAULT_ADMIN_ROLE - require( - deployment.executionLayerReceiver.hasRole( - deployment.executionLayerReceiver.DEFAULT_ADMIN_ROLE(), - address(actors.admin.ADMIN) - ), - "executionLayerReceiver: DEFAULT_ADMIN_ROLE INVALID" - ); - console.log("\u2705 executionLayerReceiver: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); - - //-------------------------------------------------------------------------------------- - //------------------- rewardsDistributor roles --------------------------------------- - //-------------------------------------------------------------------------------------- - // DEFAULT_ADMIN_ROLE - require( - deployment.rewardsDistributor.hasRole( - deployment.rewardsDistributor.DEFAULT_ADMIN_ROLE(), - address(actors.admin.ADMIN) - ), - "rewardsDistributor: DEFAULT_ADMIN_ROLE INVALID" - ); - console.log("\u2705 rewardsDistributor: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); - - // REWARDS_ADMIN_ROLE - require( - deployment.rewardsDistributor.hasRole( - deployment.rewardsDistributor.REWARDS_ADMIN_ROLE(), - address(actors.admin.REWARDS_ADMIN) - ), - "rewardsDistributor: REWARDS_ADMIN_ROLE INVALID" - ); - console.log("\u2705 rewardsDistributor: REWARDS_ADMIN_ROLE - ", vm.toString(address(actors.admin.REWARDS_ADMIN))); - - // FEE_RECEIVER - require( - deployment.rewardsDistributor.feesReceiver() == actors.admin.FEE_RECEIVER, - "rewardsDistributor: FEE_RECEIVER INVALID" - ); - console.log("\u2705 rewardsDistributor: FEE_RECEIVER - ", vm.toString(actors.admin.FEE_RECEIVER)); - - //-------------------------------------------------------------------------------------- - //------------------ stakingNodesManager roles --------------------------------------- - //-------------------------------------------------------------------------------------- - // STAKING_ADMIN_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.STAKING_ADMIN_ROLE(), - address(actors.admin.STAKING_ADMIN) - ), - "stakingNodesManager: STAKING_ADMIN_ROLE INVALID" - ); - console.log("\u2705 stakingNodesManager: STAKING_ADMIN_ROLE - ", vm.toString(address(actors.admin.STAKING_ADMIN))); - - // STAKING_NODES_OPERATOR_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.STAKING_NODES_OPERATOR_ROLE(), - address(actors.ops.STAKING_NODES_OPERATOR) - ), - "stakingNodesManager: STAKING_NODES_OPERATOR_ROLE INVALID" - ); - console.log("\u2705 stakingNodesManager: STAKING_NODES_OPERATOR_ROLE - ", vm.toString(address(actors.ops.STAKING_NODES_OPERATOR))); - - // VALIDATOR_MANAGER_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.VALIDATOR_MANAGER_ROLE(), - address(actors.ops.VALIDATOR_MANAGER) - ), - "stakingNodesManager: VALIDATOR_MANAGER_ROLE INVALID" - ); - console.log("\u2705 stakingNodesManager: VALIDATOR_MANAGER_ROLE - ", vm.toString(address(actors.ops.VALIDATOR_MANAGER))); - - // STAKING_NODE_CREATOR_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.STAKING_NODE_CREATOR_ROLE(), - address(actors.ops.STAKING_NODE_CREATOR) - ), - "stakingNodesManager: STAKING_NODE_CREATOR_ROLE INVALID" - ); - console.log("\u2705 stakingNodesManager: STAKING_NODE_CREATOR_ROLE - ", vm.toString(address(actors.ops.STAKING_NODE_CREATOR))); - - // STAKING_NODES_DELEGATOR_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.STAKING_NODES_DELEGATOR_ROLE(), - address(actors.admin.STAKING_NODES_DELEGATOR) - ), - "stakingNodesManager: STAKING_NODES_DELEGATOR_ROLE INVALID" - ); - console.log("\u2705 stakingNodesManager: STAKING_NODES_DELEGATOR_ROLE - ", vm.toString(address(actors.admin.STAKING_NODES_DELEGATOR))); - - // PAUSER_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.PAUSER_ROLE(), - address(actors.ops.PAUSE_ADMIN) - ), - "stakingNodesManager: PAUSE_ADMIN INVALID" - ); - console.log("\u2705 stakingNodesManager: PAUSE_ADMIN - ", vm.toString(address(actors.ops.PAUSE_ADMIN))); - - // UNPAUSER_ROLE - require( - deployment.stakingNodesManager.hasRole( - deployment.stakingNodesManager.UNPAUSER_ROLE(), - address(actors.admin.UNPAUSE_ADMIN) - ), - "stakingNodesManager: UNPAUSE_ADMIN INVALID" - ); - console.log("\u2705 stakingNodesManager: UNPAUSE_ADMIN - ", vm.toString(address(actors.admin.UNPAUSE_ADMIN))); - - // Check DEFAULT_SIGNER does not have DEFAULT_ADMIN_ROLE - // TODO: reenable - // require( - // !deployment.stakingNodesManager.hasRole( - // deployment.stakingNodesManager.DEFAULT_ADMIN_ROLE(), - // address(actors.eoa.DEFAULT_SIGNER) - // ), - // "stakingNodesManager: DEFAULT_SIGNER SHOULD NOT HAVE DEFAULT_ADMIN_ROLE" - // ); - // console.log("\u2705 stakingNodesManager: DEFAULT_SIGNER - DEFAULT_ADMIN_ROLE not assigned"); - - // Check DEFAULT_SIGNER does not have STAKING_ADMIN - // TODO: reenable - // require( - // !deployment.stakingNodesManager.hasRole( - // deployment.stakingNodesManager.STAKING_ADMIN_ROLE(), - // address(actors.eoa.DEFAULT_SIGNER) - // ), - // "stakingNodesManager: DEFAULT_SIGNER SHOULD NOT HAVE STAKING_ADMIN" - // ); - // console.log("\u2705 stakingNodesManager: DEFAULT_SIGNER - STAKING_ADMIN not assigned"); - - - //-------------------------------------------------------------------------------------- - //-------------------------------- ynETH roles --------------------------------------- - //-------------------------------------------------------------------------------------- - - // DEFAULT_ADMIN_ROLE - require( - deployment.ynETH.hasRole( - deployment.ynETH.DEFAULT_ADMIN_ROLE(), - address(actors.admin.ADMIN) - ), - "ynETH: DEFAULT_ADMIN_ROLE INVALID" - ); - console.log("\u2705 ynETH: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); - - // PAUSER_ROLE; - require( - deployment.ynETH.hasRole( - deployment.ynETH.PAUSER_ROLE(), - address(actors.ops.PAUSE_ADMIN) - ), - "ynETH: PAUSER_ADMIN_ROLE INVALID" - ); - console.log("\u2705 ynETH: PAUSER_ROLE - ", vm.toString(address(actors.ops.PAUSE_ADMIN))); - - // UNPAUSER_ROLE; - require( - deployment.ynETH.hasRole( - deployment.ynETH.UNPAUSER_ROLE(), - address(actors.admin.UNPAUSE_ADMIN) - ), - "ynETH: UNPAUSER_ADMIN_ROLE INVALID" - ); - console.log("\u2705 ynETH: UNPAUSER_ROLE - ", vm.toString(address(actors.admin.UNPAUSE_ADMIN))); - - } - - function verifySystemParameters() internal view { - // Verify the system parameters - require( - deployment.rewardsDistributor.feesBasisPoints() == 1000, - "ynETH: feesBasisPoints INVALID" - ); - console.log("\u2705 ynETH: feesBasisPoints - Value:", deployment.rewardsDistributor.feesBasisPoints()); - - require( - deployment.ynETH.depositsPaused() == false, - "ynETH: depositsPaused INVALID" - ); - console.log("\u2705 ynETH: depositsPaused - Value:", deployment.ynETH.depositsPaused()); - - require( - deployment.stakingNodesManager.maxNodeCount() == 10, - "ynETH: maxNodeCount INVALID" - ); - console.log("\u2705 ynETH: maxNodeCount - Value:", deployment.stakingNodesManager.maxNodeCount()); - - require( - deployment.stakingNodesManager.validatorRegistrationPaused() == false, - "ynETH: validatorRegistrationPaused INVALID" - ); - console.log("\u2705 ynETH: validatorRegistrationPaused - Value:", deployment.stakingNodesManager.validatorRegistrationPaused()); - - console.log("\u2705 All system parameters verified successfully"); - } - - function verifyContractDependencies() internal { - - verifyYnETHDependencies(); - verifyStakingNodesManagerDependencies(); - verifyRewardsDistributorDependencies(); - verifyAllStakingNodeDependencies(); - - console.log("\u2705 All contract dependencies verified successfully"); - } - - function verifyYnETHDependencies() internal view { - // Verify ynETH contract dependencies - require( - address(deployment.ynETH.rewardsDistributor()) == address(deployment.rewardsDistributor), - "ynETH: RewardsDistributor dependency mismatch" - ); - require( - address(deployment.ynETH.stakingNodesManager()) == address(deployment.stakingNodesManager), - "ynETH: StakingNodesManager dependency mismatch" - ); - - console.log("\u2705 ynETH dependencies verified successfully"); - } - - function verifyRewardsDistributorDependencies() internal view { - // Verify RewardsDistributor contract dependencies - require( - address(deployment.rewardsDistributor.ynETH()) == address(deployment.ynETH), - "RewardsDistributor: ynETH dependency mismatch" - ); - require( - address(deployment.rewardsDistributor.executionLayerReceiver()) == address(deployment.executionLayerReceiver), - "RewardsDistributor: executionLayerReceiver dependency mismatch" - ); - require( - address(deployment.rewardsDistributor.consensusLayerReceiver()) == address(deployment.consensusLayerReceiver), - "RewardsDistributor: consensusLayerReceiver dependency mismatch" - ); - - console.log("\u2705 RewardsDistributor dependencies verified"); - } - - function verifyStakingNodesManagerDependencies() internal view { - require( - address(deployment.stakingNodesManager.ynETH()) == address(deployment.ynETH), - "StakingNodesManager: ynETH dependency mismatch" - ); - - require( - address(deployment.stakingNodesManager.rewardsDistributor()) == address(deployment.rewardsDistributor), - "StakingNodesManager: rewardsDistributor dependency mismatch" - ); - - require( - address(deployment.stakingNodesManager.eigenPodManager()) == chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS, - "StakingNodesManager: eigenPodManager dependency mismatch" - ); - require( - address(deployment.stakingNodesManager.depositContractEth2()) == chainAddresses.ethereum.DEPOSIT_2_ADDRESS, - "StakingNodesManager: depositContractEth2 dependency mismatch" - ); - require( - address(deployment.stakingNodesManager.delegationManager()) == chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS, - "StakingNodesManager: delegationManager dependency mismatch" - ); - require( - address(deployment.stakingNodesManager.delayedWithdrawalRouter()) == chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS, - "StakingNodesManager: delayedWithdrawalRouter dependency mismatch" - ); - require( - address(deployment.stakingNodesManager.strategyManager()) == chainAddresses.eigenlayer.STRATEGY_MANAGER_ADDRESS, - "StakingNodesManager: strategyManager dependency mismatch" - ); - - require( - address(deployment.stakingNodesManager.upgradeableBeacon().implementation()) == address(deployment.stakingNodeImplementation), - "StakingNodesManager: upgradeableBeacon implementation mismatch" - ); - - console.log("\u2705 StakingNodesManager dependencies verified"); - } - - function verifyAllStakingNodeDependencies() internal view { - IStakingNode[] memory stakingNodes = deployment.stakingNodesManager.getAllNodes(); - for (uint256 i = 0; i < stakingNodes.length; i++) { - IStakingNode stakingNode = stakingNodes[i]; - require( - address(stakingNode.stakingNodesManager()) == address(deployment.stakingNodesManager), - "StakingNode: StakingNodesManager dependency mismatch" - ); - address storedPod = address(IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS).ownerToPod(address(stakingNode))); - - console.log("StakingNode address:", address(stakingNode)); - console.log("EigenPod address:", address(stakingNode.eigenPod())); - assert( - address(stakingNode.eigenPod()) == storedPod - ); - console.log("\u2705 StakingNode dependencies verified for node", i); - } - } -} \ No newline at end of file diff --git a/script/commands/DepositToYnLSDe.s.sol b/script/commands/DepositToYnLSDe.s.sol index 896d0ed2e..369a19785 100644 --- a/script/commands/DepositToYnLSDe.s.sol +++ b/script/commands/DepositToYnLSDe.s.sol @@ -24,8 +24,9 @@ contract DepositToYnLSDe is BaseYnEigenScript { uint256 public constant AMOUNT = 0.1 ether; function run() public { - address token = _getTokenAddress(vm.prompt("Token (`sfrxETH`, `wstETH`, `mETH` and `rETH` (holesky only))")); - uint256 path = vm.parseUint(vm.prompt("Path (`0` for deposit or `1` for send")); + // TODO: FIXME: fix the prompts + address token; // = _getTokenAddress(vm.prompt("Token (`sfrxETH`, `wstETH`, `mETH` and `rETH` (holesky only))")); + uint256 path; // = vm.parseUint(vm.prompt("Path (`0` for deposit or `1` for send")); run(path, token); } diff --git a/script/delegation/DelegateTransactionDataBuilder.sol b/script/delegation/DelegateTransactionDataBuilder.sol new file mode 100644 index 000000000..7d71f730f --- /dev/null +++ b/script/delegation/DelegateTransactionDataBuilder.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {BaseScript} from "script/BaseScript.s.sol"; +import {stdJson} from "lib/forge-std/src/StdJson.sol"; +import {PooledDepositsVault} from "src/PooledDepositsVault.sol"; // Renamed from PooledDeposits to PooledDepositsVault +import {ActorAddresses} from "script/Actors.sol"; +import {console} from "lib/forge-std/src/console.sol"; +import {IStakingNode} from "src/interfaces/IStakingNode.sol"; +import {ISignatureUtils} from "lib/eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; +import {ContractAddresses} from "script/ContractAddresses.sol"; + +contract DelegateTransactionBuilder is BaseScript { + + + function run() external { + + address[] memory stakingNodes = new address[](5); + stakingNodes[0] = 0x412946D1c4F7e55E3F4B9dD1bA6D619E29Af9bA2; + stakingNodes[1] = 0xAc4B7CA94c004A6D7cE9B62fb9d86DF8f6CcFc26; + stakingNodes[2] = 0xAEBDCD5285988009C1C4cC05a8DDdd29E42304C7; + stakingNodes[3] = 0x77F7d153Bd9e25293a95AEDFE8087F3e24D73c9e; + stakingNodes[4] = 0xc9170a5C286a6D8C80b07d20E087e20f273A36A1; + + ContractAddresses contractAddresses = new ContractAddresses(); + ContractAddresses.ChainAddresses memory chainAddresses = contractAddresses.getChainAddresses(block.chainid); + IStakingNodesManager stakingNodesManager = IStakingNodesManager(chainAddresses.yn.STAKING_NODES_MANAGER_ADDRESS); + IStakingNode[] memory allNodes = stakingNodesManager.getAllNodes(); + require(allNodes.length == stakingNodes.length, "Node count mismatch."); + + for (uint i = 0; i < stakingNodes.length; i++) { + require(address(allNodes[i]) == stakingNodes[i], "Node address mismatch."); + } + + address OPERATOR_A41 = 0xa83e07353A9ED2aF88e7281a2fA7719c01356D8e; + address OPERATOR_P2P = 0xDbEd88D83176316fc46797B43aDeE927Dc2ff2F5; + + address[] memory operators = new address[](5); + operators[0] = OPERATOR_A41; + operators[1] = OPERATOR_A41; + operators[2] = OPERATOR_A41; + operators[3] = OPERATOR_A41; + operators[4] = OPERATOR_P2P; + + for (uint i = 0; i < stakingNodes.length; i++) { + address currentOperator = operators[i]; + + // Generate tx data for delegating to an operator + bytes memory delegateTxData = abi.encodeWithSelector( + IStakingNode.delegate.selector, + currentOperator, + ISignatureUtils.SignatureWithExpiry({signature: "", expiry: 0}), + bytes32(0) + ); + console.log("Node address:", stakingNodes[i]); + console.log("Index:", i); + console.log("Delegating to operator:", currentOperator); + console.log("Delegate transaction data:", vm.toString(abi.encodePacked(delegateTxData))); + } + } + +} \ No newline at end of file diff --git a/script/verification/verify-bytecode-ynlsde.sh b/script/verification/verify-bytecode-ynlsde.sh deleted file mode 100644 index c54deba1a..000000000 --- a/script/verification/verify-bytecode-ynlsde.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/bin/bash - -# Read the deployment JSON file -DEPLOYMENT_FILE="./deployments/YnLSDe-1.json" - -# List of contracts to verify -contracts=("YnLSDe" "assetRegistry" "eigenStrategyManager" "tokenStakingNodesManager" "ynEigenDepositAdapter" "rateProvider") - -# Note: verify "ynEigenViewer" manually. more difficult to craft parameter bytecode verification - -# Read the Etherscan API key from .env file -ETHERSCAN_API_KEY=$(grep ETHERSCAN_API_KEY .env | cut -d '=' -f2 | tr -d '[:space:]') - -# Log ETHERSCAN_API_KEY (masked for security) -echo "ETHERSCAN_API_KEY: ${ETHERSCAN_API_KEY:0:4}...${ETHERSCAN_API_KEY: -4}" - - -get_solidity_contract_name() { - local contract_key=$1 - local solidity_contract="" - - if [ "$contract_key" == "YnLSDe" ]; then - solidity_contract="ynEigen" - elif [ "$contract_key" == "assetRegistry" ]; then - solidity_contract="AssetRegistry" - elif [ "$contract_key" == "eigenStrategyManager" ]; then - solidity_contract="EigenStrategyManager" - elif [ "$contract_key" == "tokenStakingNodesManager" ]; then - solidity_contract="TokenStakingNodesManager" - elif [ "$contract_key" == "ynEigenDepositAdapter" ]; then - solidity_contract="ynEigenDepositAdapter" - elif [ "$contract_key" == "rateProvider" ]; then - solidity_contract="LSDRateProvider" - elif [ "$contract_key" == "ynEigenViewer" ]; then - solidity_contract="ynEigenViewer" - elif [ "$contract_key" == "upgradeTimelock" ]; then - solidity_contract="TimelockController" - elif [ "$contract_key" == "tokenStakingNodeImplementation" ]; then - solidity_contract="TokenStakingNode" - else - echo "Error: Unknown contract key '$contract_key'" >&2 - return 1 - fi - - echo "$solidity_contract" -} - -# Function to extract proxy and implementation addresses -extract_addresses() { - local key=$1 - local proxy_address=$(jq -r ".[\"proxy-$key\"]" "$DEPLOYMENT_FILE") - local impl_address=$(jq -r ".[\"implementation-$key\"]" "$DEPLOYMENT_FILE") - local proxy_admin=$(jq -r ".[\"proxyAdmin-$key\"]" "$DEPLOYMENT_FILE") - echo "$proxy_address $impl_address $proxy_admin" -} - -verify_implementation() { - local impl_address=$1 - local solidity_contract=$2 - local contract_name=$3 - - # Get bytecode from Etherscan - local etherscan_sha256=$(curl -X GET "https://api.etherscan.io/api?module=proxy&action=eth_getCode&address=$impl_address&tag=latest&apikey=$ETHERSCAN_API_KEY" | jq -r '.result' | sha256sum) - - # Get local bytecode - local local_sha256=$(cat "out/$solidity_contract.sol/$solidity_contract.json" | jq -r '.deployedBytecode.object' | sed 's/"//g' | sha256sum) - - # Compare SHA256 hashes - if [ "$etherscan_sha256" = "$local_sha256" ]; then - echo "✅ Bytecode verification successful for $contract_name" - else - echo -e "❌ Error: Bytecode mismatch for $contract_name" - echo -e "❌ Etherscan SHA256: $etherscan_sha256" - echo -e "❌ Local SHA256: $local_sha256" - return 1 - fi -} - -# Function to verify a contract -verify_contract() { - local proxy_address=$1 - local impl_address=$2 - local proxy_admin=$3 - local contract_name=$4 - - # Get the Solidity contract name - local solidity_contract=$(get_solidity_contract_name "$contract_name") - if [ -z "$solidity_contract" ]; then - echo "Error: No Solidity contract name found for $contract_name" - return 1 - fi - - echo "Verifying $contract_name (Solidity contract: $solidity_contract)..." - - verify_implementation "$impl_address" "$solidity_contract" "$contract_name" -} - -# Extract upgradeTimelock address -upgradeTimelock=$(jq -r '.upgradeTimelock' "$DEPLOYMENT_FILE") - -verify_contract "" $upgradeTimelock "" "upgradeTimelock" - -# Extract tokenStakingNodeImplementation address -tokenStakingNodeImplementation=$(jq -r '.tokenStakingNodeImplementation' "$DEPLOYMENT_FILE") - -verify_contract "" $tokenStakingNodeImplementation "" "tokenStakingNodeImplementation" - - - -for contract in "${contracts[@]}"; do - read proxy impl proxy_admin <<< $(extract_addresses $contract) - - echo "Extracted addresses for $contract:" - echo " Proxy: $proxy" - echo " Implementation: $impl" - echo " Proxy Admin: $proxy_admin" - echo "" - verify_contract $proxy $impl $proxy_admin $contract -done \ No newline at end of file diff --git a/script/verification/verify-ynlsde-deployment.sh b/script/verification/verify-ynlsde-deployment.sh deleted file mode 100644 index 5232575e4..000000000 --- a/script/verification/verify-ynlsde-deployment.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/bash - -# Read the deployment JSON file -DEPLOYMENT_FILE="./deployments/YnLSDe-17000.json" -# Read the Etherscan API key from .env file -ETHERSCAN_API_KEY=$(grep ETHERSCAN_API_KEY .env | cut -d '=' -f2) -# Read the RPC URL from .env file -RPC_URL=$(grep RPC_URL .env | cut -d '=' -f2) - -# Check if RPC_URL is empty -if [ -z "$RPC_URL" ]; then - echo "Error: RPC_URL is not set in the .env file" - exit 1 -fi - -echo "Using RPC URL: $RPC_URL" - -# Extract chain ID from RPC_URL -CHAIN_ID=$(cast chain-id --rpc-url $RPC_URL) -echo "Chain ID: $CHAIN_ID" - -# List of contracts to verify -contracts=("YnLSDe" "assetRegistry" "eigenStrategyManager" "tokenStakingNodesManager" "ynEigenDepositAdapter" "rateProvider" "ynEigenViewer") - -get_solidity_contract_name() { - local contract_key=$1 - local solidity_contract="" - - if [ "$contract_key" == "YnLSDe" ]; then - solidity_contract="ynEigen" - elif [ "$contract_key" == "assetRegistry" ]; then - solidity_contract="AssetRegistry" - elif [ "$contract_key" == "eigenStrategyManager" ]; then - solidity_contract="EigenStrategyManager" - elif [ "$contract_key" == "tokenStakingNodesManager" ]; then - solidity_contract="TokenStakingNodesManager" - elif [ "$contract_key" == "ynEigenDepositAdapter" ]; then - solidity_contract="ynEigenDepositAdapter" - elif [ "$contract_key" == "rateProvider" ]; then - solidity_contract="LSDRateProvider" - elif [ "$contract_key" == "ynEigenViewer" ]; then - solidity_contract="ynEigenViewer" - else - echo "Error: Unknown contract key '$contract_key'" >&2 - return 1 - fi - - echo "$solidity_contract" -} - -# Function to extract proxy and implementation addresses -extract_addresses() { - local key=$1 - local proxy_address=$(jq -r ".[\"proxy-$key\"]" "$DEPLOYMENT_FILE") - local impl_address=$(jq -r ".[\"implementation-$key\"]" "$DEPLOYMENT_FILE") - local proxy_admin=$(jq -r ".[\"proxyAdmin-$key\"]" "$DEPLOYMENT_FILE") - echo "$proxy_address $impl_address $proxy_admin" -} - -timelock_address=$(jq -r '.upgradeTimelock' "$DEPLOYMENT_FILE") - -# Function to verify a contract -verify_contract() { - local proxy_address=$1 - local impl_address=$2 - local proxy_admin=$3 - local contract_name=$4 - - # Get the Solidity contract name - local solidity_contract=$(get_solidity_contract_name "$contract_name") - if [ -z "$solidity_contract" ]; then - echo "Error: No Solidity contract name found for $contract_name" - return 1 - fi - - echo "Verifying $contract_name (Solidity contract: $solidity_contract)..." - - - # Verify the implementation contract - if [ "$contract_name" == "ynEigenViewer" ]; then - - # Extract proxy addresses for ynEigen, eigenStrategyManager, and tokenStakingNodesManager - read YnEigen _ _ <<< $(extract_addresses YnLSDe) - read AssetRegistry _ _ <<< $(extract_addresses assetRegistry) - read TokenStakingNodesManager _ _ <<< $(extract_addresses tokenStakingNodesManager) - read RateProvider _ _ <<< $(extract_addresses rateProvider) - - echo "Verifying ynEigenViewer..." - echo "AssetRegistry address: $AssetRegistry" - echo "YnEigen address: $YnEigen" - echo "TokenStakingNodesManager address: $TokenStakingNodesManager" - echo "RateProvider address: $RateProvider" - - - # Encode constructor arguments for ynEigenViewer - constructor_args=$(cast abi-encode "constructor(address,address,address,address)" $AssetRegistry $YnEigen $TokenStakingNodesManager $RateProvider) - - # Verify the ynEigenViewer implementation contract - forge verify-contract $impl_address $solidity_contract \ - --constructor-args $constructor_args \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL - else - forge verify-contract $impl_address $solidity_contract \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL - fi - - # Verify the proxy contract - forge verify-contract \ - --constructor-args $(cast abi-encode "constructor(address,address,bytes)" $impl_address $timelock_address "0x") \ - $proxy_address TransparentUpgradeableProxy \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL - - # FIXME: this still doesn't work. - # Verify the proxy admin contract - forge verify-contract \ - --constructor-args $(cast abi-encode "constructor(address)" $timelock_address) \ - $proxy_admin ProxyAdmin \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL -} - -# Verify each contract -for contract in "${contracts[@]}"; do - read proxy impl proxy_admin <<< $(extract_addresses $contract) - verify_contract $proxy $impl $proxy_admin $contract -done -# Verify TokenStakingNode implementation -token_staking_node_impl=$(jq -r '.tokenStakingNodeImplementation' "$DEPLOYMENT_FILE") -forge verify-contract $token_staking_node_impl TokenStakingNode \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL - -# Define delay based on chain ID -if [ "$CHAIN_ID" = "17000" ]; then - delay=$((15 * 60)) # 15 minutes in seconds -elif [ "$CHAIN_ID" = "1" ]; then - delay=$((3 * 24 * 60 * 60)) # 3 days in seconds -else - echo "Unsupported chain ID: $CHAIN_ID" - exit 1 -fi -echo "Timelock delay: $delay seconds" - - -# Load YNDev and YNSecurityCouncil addresses from deployment file -YNDev=$(jq -r '.YNDev' "$DEPLOYMENT_FILE") -YNSecurityCouncil=$(jq -r '.YnSecurityCouncil' "$DEPLOYMENT_FILE") - -# Log YNDev and YNSecurityCouncil addresses -echo "YNDev address: $YNDev" -echo "YNSecurityCouncil address: $YNSecurityCouncil" - -# Encode constructor arguments; assumes only one of each. -constructor_args=$(cast abi-encode "constructor(uint256,address[],address[],address)" $delay "[$YNDev]" "[$YNSecurityCouncil]" $YNSecurityCouncil) - -echo "Timelock constructor arguments: $constructor_args" - -# Verify TimelockController -forge verify-contract $timelock_address TimelockController \ - --constructor-args $constructor_args \ - --etherscan-api-key $ETHERSCAN_API_KEY \ - --rpc-url $RPC_URL - -echo "Verification process completed." - - diff --git a/script/BaseYnETHScript.s.sol b/script/ynETH/BaseYnETHScript.s.sol similarity index 58% rename from script/BaseYnETHScript.s.sol rename to script/ynETH/BaseYnETHScript.s.sol index c7b93f43e..9258a494b 100644 --- a/script/BaseYnETHScript.s.sol +++ b/script/ynETH/BaseYnETHScript.s.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: BSD 3-Clause License pragma solidity ^0.8.24; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; + import {StakingNodesManager} from "src/StakingNodesManager.sol"; import {StakingNode} from "src/StakingNode.sol"; import {RewardsReceiver} from "src/RewardsReceiver.sol"; @@ -11,11 +14,29 @@ import {Script} from "lib/forge-std/src/Script.sol"; import {Utils} from "script/Utils.sol"; import {ActorAddresses} from "script/Actors.sol"; import {BaseScript} from "script/BaseScript.s.sol"; +import {WithdrawalQueueManager} from "src/WithdrawalQueueManager.sol"; +import {ynETHRedemptionAssetsVault} from "src/ynETHRedemptionAssetsVault.sol"; +import {WithdrawalsProcessor} from "src/WithdrawalsProcessor.sol"; +import {ynViewer} from "src/ynViewer.sol"; + + import {console} from "lib/forge-std/src/console.sol"; abstract contract BaseYnETHScript is BaseScript { using stdJson for string; + struct DeploymentProxies { + ProxyAddresses ynETH; + ProxyAddresses stakingNodesManager; + ProxyAddresses executionLayerReceiver; + ProxyAddresses consensusLayerReceiver; + ProxyAddresses rewardsDistributor; + ProxyAddresses withdrawalQueueManager; + ProxyAddresses ynETHRedemptionAssetsVault; + ProxyAddresses withdrawalsProcessor; + ProxyAddresses ynViewer; + } + struct Deployment { ynETH ynETH; StakingNodesManager stakingNodesManager; @@ -23,6 +44,11 @@ abstract contract BaseYnETHScript is BaseScript { RewardsReceiver consensusLayerReceiver; RewardsDistributor rewardsDistributor; StakingNode stakingNodeImplementation; + WithdrawalQueueManager withdrawalQueueManager; + ynETHRedemptionAssetsVault ynETHRedemptionAssetsVaultInstance; + WithdrawalsProcessor withdrawalsProcessor; + ynViewer ynViewer; + DeploymentProxies proxies; } function getDeploymentFile() internal virtual view returns (string memory) { @@ -39,6 +65,13 @@ abstract contract BaseYnETHScript is BaseScript { serializeProxyElements(json, "executionLayerReceiver", address(deployment.executionLayerReceiver)); serializeProxyElements(json, "consensusLayerReceiver", address(deployment.consensusLayerReceiver)); serializeProxyElements(json, "rewardsDistributor", address(deployment.rewardsDistributor)); + serializeProxyElements(json, "ynViewer", address(deployment.ynViewer)); + + // withdrawals + serializeProxyElements(json, "withdrawalQueueManager", address(deployment.withdrawalQueueManager)); + serializeProxyElements(json, "ynETHRedemptionAssetsVault", address(deployment.ynETHRedemptionAssetsVaultInstance)); + serializeProxyElements(json, "withdrawalsProcessor", address(deployment.withdrawalsProcessor)); + vm.serializeAddress(json, "stakingNodeImplementation", address(deployment.stakingNodeImplementation)); ActorAddresses.Actors memory actors = getActors(); @@ -65,13 +98,39 @@ abstract contract BaseYnETHScript is BaseScript { string memory deploymentFile = getDeploymentFile(); string memory jsonContent = vm.readFile(deploymentFile); Deployment memory deployment; + DeploymentProxies memory proxies; + deployment.ynETH = ynETH(payable(jsonContent.readAddress(".proxy-ynETH"))); + proxies.ynETH = loadProxyAddresses(jsonContent, "ynETH"); + deployment.stakingNodesManager = StakingNodesManager(payable(jsonContent.readAddress(".proxy-stakingNodesManager"))); + proxies.stakingNodesManager = loadProxyAddresses(jsonContent, "stakingNodesManager"); + deployment.executionLayerReceiver = RewardsReceiver(payable(jsonContent.readAddress(".proxy-executionLayerReceiver"))); + proxies.executionLayerReceiver = loadProxyAddresses(jsonContent, "executionLayerReceiver"); + deployment.consensusLayerReceiver = RewardsReceiver(payable(jsonContent.readAddress(".proxy-consensusLayerReceiver"))); + proxies.consensusLayerReceiver = loadProxyAddresses(jsonContent, "consensusLayerReceiver"); + deployment.rewardsDistributor = RewardsDistributor(payable(jsonContent.readAddress(".proxy-rewardsDistributor"))); - deployment.stakingNodeImplementation = StakingNode(payable(jsonContent.readAddress(".stakingNodeImplementation"))); + proxies.rewardsDistributor = loadProxyAddresses(jsonContent, "rewardsDistributor"); + + deployment.ynViewer = ynViewer(payable(jsonContent.readAddress(".proxy-ynViewer"))); + proxies.ynViewer = loadProxyAddresses(jsonContent, "ynViewer"); + + deployment.withdrawalQueueManager = WithdrawalQueueManager(payable(jsonContent.readAddress(".proxy-withdrawalQueueManager"))); + proxies.withdrawalQueueManager = loadProxyAddresses(jsonContent, "withdrawalQueueManager"); + deployment.ynETHRedemptionAssetsVaultInstance = ynETHRedemptionAssetsVault(payable(jsonContent.readAddress(".proxy-ynETHRedemptionAssetsVault"))); + proxies.ynETHRedemptionAssetsVault = loadProxyAddresses(jsonContent, "ynETHRedemptionAssetsVault"); + + deployment.withdrawalsProcessor = WithdrawalsProcessor(payable(jsonContent.readAddress(".proxy-withdrawalsProcessor"))); + proxies.withdrawalsProcessor = loadProxyAddresses(jsonContent, "withdrawalsProcessor"); + + deployment.proxies = proxies; + + deployment.stakingNodeImplementation = StakingNode(payable(jsonContent.readAddress(".stakingNodeImplementation"))); + return deployment; } } \ No newline at end of file diff --git a/script/DeployYieldNest.s.sol b/script/ynETH/DeployYnETH.s.sol similarity index 89% rename from script/DeployYieldNest.s.sol rename to script/ynETH/DeployYnETH.s.sol index 048c7f257..d8963396d 100644 --- a/script/DeployYieldNest.s.sol +++ b/script/ynETH/DeployYnETH.s.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.24; import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; +// import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {IDepositContract} from "src/external/ethereum/IDepositContract.sol"; import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; @@ -20,7 +20,7 @@ import {RewardsDistributor} from "src/RewardsDistributor.sol"; import {ynETH} from "src/ynETH.sol"; import {ContractAddresses} from "script/ContractAddresses.sol"; import {BaseScript} from "script/BaseScript.s.sol"; -import {BaseYnETHScript} from "script/BaseYnETHScript.s.sol"; +import {BaseYnETHScript} from "script/ynETH/BaseYnETHScript.s.sol"; import {ActorAddresses} from "script/Actors.sol"; import {console} from "lib/forge-std/src/console.sol"; @@ -42,7 +42,7 @@ contract DeployYieldNest is BaseYnETHScript { IEigenPodManager public eigenPodManager; IDelegationManager public delegationManager; - IDelayedWithdrawalRouter public delayedWithdrawalRouter; + // IDelayedWithdrawalRouter public delayedWithdrawalRouter; IStrategyManager public strategyManager; IDepositContract public depositContract; IWETH public weth; @@ -72,7 +72,7 @@ contract DeployYieldNest is BaseYnETHScript { ContractAddresses.ChainAddresses memory chainAddresses = contractAddresses.getChainAddresses(block.chainid); eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS); delegationManager = IDelegationManager(chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS); - delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); + // delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); strategyManager = IStrategyManager(chainAddresses.eigenlayer.STRATEGY_MANAGER_ADDRESS); depositContract = IDepositContract(chainAddresses.ethereum.DEPOSIT_2_ADDRESS); weth = IWETH(chainAddresses.ethereum.WETH_ADDRESS); @@ -131,7 +131,7 @@ contract DeployYieldNest is BaseYnETHScript { depositContract: depositContract, eigenPodManager: eigenPodManager, delegationManager: delegationManager, - delayedWithdrawalRouter: delayedWithdrawalRouter, + // delayedWithdrawalRouter: delayedWithdrawalRouter, strategyManager: strategyManager }); stakingNodesManager.initialize(stakingNodesManagerInit); @@ -162,14 +162,13 @@ contract DeployYieldNest is BaseYnETHScript { vm.stopBroadcast(); - Deployment memory deployment = Deployment({ - ynETH: yneth, - stakingNodesManager: stakingNodesManager, - executionLayerReceiver: executionLayerReceiver, - consensusLayerReceiver: consensusLayerReceiver, // Adding consensusLayerReceiver to the deployment - rewardsDistributor: rewardsDistributor, - stakingNodeImplementation: stakingNodeImplementation - }); + Deployment memory deployment; + deployment.ynETH = yneth; + deployment.stakingNodesManager = stakingNodesManager; + deployment.executionLayerReceiver = executionLayerReceiver; + deployment.consensusLayerReceiver = consensusLayerReceiver; // Adding consensusLayerReceiver to the deployment + deployment.rewardsDistributor = rewardsDistributor; + deployment.stakingNodeImplementation = stakingNodeImplementation; saveDeployment(deployment); diff --git a/script/ynETH/VerifyYnETH.s.sol b/script/ynETH/VerifyYnETH.s.sol new file mode 100644 index 000000000..ff8901617 --- /dev/null +++ b/script/ynETH/VerifyYnETH.s.sol @@ -0,0 +1,699 @@ +/// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {ContractAddresses} from "script/ContractAddresses.sol"; +import {BaseYnETHScript} from "script/ynETH/BaseYnETHScript.s.sol"; +import { IEigenPodManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; +import {IStakingNode} from "src/interfaces/IStakingNode.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {Utils} from "script/Utils.sol"; + +import {ActorAddresses} from "script/Actors.sol"; +import {console} from "lib/forge-std/src/console.sol"; + +contract Verify is BaseYnETHScript { + + Deployment deployment; + ActorAddresses.Actors actors; + ContractAddresses.ChainAddresses chainAddresses; + + function run() external { + + ContractAddresses contractAddresses = new ContractAddresses(); + chainAddresses = contractAddresses.getChainAddresses(block.chainid); + + deployment = loadDeployment(); + actors = getActors(); + + verifyProxies(); + verifyRoles(); + verifySystemParameters(); + verifyContractDependencies(); + veryifySanityChecks(); + } + + function verifyProxyContract( + address contractAddress, + string memory contractName, + ProxyAddresses memory proxyAddresses + ) internal view { + // Verify PROXY_ADMIN_OWNER + address proxyAdminAddress = Utils.getTransparentUpgradeableProxyAdminAddress(contractAddress); + address proxyAdminOwner = ProxyAdmin(proxyAdminAddress).owner(); + require( + proxyAdminOwner == actors.admin.PROXY_ADMIN_OWNER, + string.concat(contractName, ": PROXY_ADMIN_OWNER mismatch, expected: ", vm.toString(actors.admin.PROXY_ADMIN_OWNER), ", got: ", vm.toString(proxyAdminOwner)) + ); + console.log(string.concat("\u2705 ", contractName, ": PROXY_ADMIN_OWNER - ", vm.toString(proxyAdminOwner))); + + // Verify ProxyAdmin address + require( + proxyAdminAddress == address(proxyAddresses.proxyAdmin), + string.concat(contractName, ": ProxyAdmin address mismatch, expected: ", vm.toString(address(proxyAddresses.proxyAdmin)), ", got: ", vm.toString(proxyAdminAddress)) + ); + console.log(string.concat("\u2705 ", contractName, ": ProxyAdmin address - ", vm.toString(proxyAdminAddress))); + + // Verify Implementation address + address implementationAddress = Utils.getTransparentUpgradeableProxyImplementationAddress(contractAddress); + require( + implementationAddress == proxyAddresses.implementation, + string.concat(contractName, ": Implementation address mismatch, expected: ", vm.toString(proxyAddresses.implementation), ", got: ", vm.toString(implementationAddress)) + ); + console.log(string.concat("\u2705 ", contractName, ": Implementation address - ", vm.toString(implementationAddress))); + } + + function verifyProxies() internal view { + verifyProxyContract( + address(deployment.ynETH), + "ynETH", + deployment.proxies.ynETH + ); + + verifyProxyContract( + address(deployment.rewardsDistributor), + "rewardsDistributor", + deployment.proxies.rewardsDistributor + ); + + verifyProxyContract( + address(deployment.stakingNodesManager), + "stakingNodesManager", + deployment.proxies.stakingNodesManager + ); + + verifyProxyContract( + address(deployment.consensusLayerReceiver), + "consensusLayerReceiver", + deployment.proxies.consensusLayerReceiver + ); + + verifyProxyContract( + address(deployment.executionLayerReceiver), + "executionLayerReceiver", + deployment.proxies.executionLayerReceiver + ); + + verifyProxyContract( + address(deployment.ynViewer), + "ynViewer", + deployment.proxies.ynViewer + ); + + verifyProxyContract( + address(deployment.withdrawalQueueManager), + "withdrawalQueueManager", + deployment.proxies.withdrawalQueueManager + ); + + verifyProxyContract( + address(deployment.ynETHRedemptionAssetsVaultInstance), + "ynETHRedemptionAssetsVault", + deployment.proxies.ynETHRedemptionAssetsVault + ); + + verifyProxyContract( + address(deployment.withdrawalsProcessor), + "withdrawalsProcessor", + deployment.proxies.withdrawalsProcessor + ); + } + + function verifyRoles() internal view { + + //-------------------------------------------------------------------------------------- + //---------------- consesusLayerReceiver roles --------------------------------------- + //-------------------------------------------------------------------------------------- + // WITHDRAWER_ROLE + require( + deployment.consensusLayerReceiver.hasRole( + deployment.consensusLayerReceiver.WITHDRAWER_ROLE(), + address(deployment.rewardsDistributor) + ), + "consensusLayerReceiver: WITHDRAWER_ROLE INVALID" + ); + console.log("\u2705 consensusLayerReceiver: WITHDRAWER_ROLE - ", vm.toString(address(deployment.rewardsDistributor))); + + // DEFAULT_ADMIN_ROLE + require( + deployment.consensusLayerReceiver.hasRole( + deployment.consensusLayerReceiver.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "consensusLayerReceiver: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 consensusLayerReceiver: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + + //-------------------------------------------------------------------------------------- + //--------------- executionLayerReceiver roles --------------------------------------- + //-------------------------------------------------------------------------------------- + // WITHDRAWER_ROLE + require( + deployment.executionLayerReceiver.hasRole( + deployment.executionLayerReceiver.WITHDRAWER_ROLE(), + address(deployment.rewardsDistributor) + ), + "executionLayerReceiver: WITHDRAWER_ROLE INVALID" + ); + console.log("\u2705 executionLayerReceiver: WITHDRAWER_ROLE - ", vm.toString(address(deployment.rewardsDistributor))); + + // DEFAULT_ADMIN_ROLE + require( + deployment.executionLayerReceiver.hasRole( + deployment.executionLayerReceiver.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "executionLayerReceiver: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 executionLayerReceiver: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + //-------------------------------------------------------------------------------------- + //------------------- rewardsDistributor roles --------------------------------------- + //-------------------------------------------------------------------------------------- + // DEFAULT_ADMIN_ROLE + require( + deployment.rewardsDistributor.hasRole( + deployment.rewardsDistributor.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "rewardsDistributor: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 rewardsDistributor: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // REWARDS_ADMIN_ROLE + require( + deployment.rewardsDistributor.hasRole( + deployment.rewardsDistributor.REWARDS_ADMIN_ROLE(), + address(actors.admin.REWARDS_ADMIN) + ), + "rewardsDistributor: REWARDS_ADMIN_ROLE INVALID" + ); + console.log("\u2705 rewardsDistributor: REWARDS_ADMIN_ROLE - ", vm.toString(address(actors.admin.REWARDS_ADMIN))); + + // FEE_RECEIVER + require( + deployment.rewardsDistributor.feesReceiver() == actors.admin.FEE_RECEIVER, + "rewardsDistributor: FEE_RECEIVER INVALID" + ); + console.log("\u2705 rewardsDistributor: FEE_RECEIVER - ", vm.toString(actors.admin.FEE_RECEIVER)); + + //-------------------------------------------------------------------------------------- + //------------------ stakingNodesManager roles --------------------------------------- + //-------------------------------------------------------------------------------------- + // STAKING_ADMIN_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.STAKING_ADMIN_ROLE(), + address(actors.admin.STAKING_ADMIN) + ), + "stakingNodesManager: STAKING_ADMIN_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: STAKING_ADMIN_ROLE - ", vm.toString(address(actors.admin.STAKING_ADMIN))); + + // STAKING_NODES_OPERATOR_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.STAKING_NODES_OPERATOR_ROLE(), + address(actors.ops.STAKING_NODES_OPERATOR) + ), + "stakingNodesManager: STAKING_NODES_OPERATOR_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: STAKING_NODES_OPERATOR_ROLE - ", vm.toString(address(actors.ops.STAKING_NODES_OPERATOR))); + + // VALIDATOR_MANAGER_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.VALIDATOR_MANAGER_ROLE(), + address(actors.ops.VALIDATOR_MANAGER) + ), + "stakingNodesManager: VALIDATOR_MANAGER_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: VALIDATOR_MANAGER_ROLE - ", vm.toString(address(actors.ops.VALIDATOR_MANAGER))); + + // STAKING_NODE_CREATOR_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.STAKING_NODE_CREATOR_ROLE(), + address(actors.ops.STAKING_NODE_CREATOR) + ), + "stakingNodesManager: STAKING_NODE_CREATOR_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: STAKING_NODE_CREATOR_ROLE - ", vm.toString(address(actors.ops.STAKING_NODE_CREATOR))); + + // STAKING_NODES_DELEGATOR_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.STAKING_NODES_DELEGATOR_ROLE(), + address(actors.admin.STAKING_NODES_DELEGATOR) + ), + "stakingNodesManager: STAKING_NODES_DELEGATOR_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: STAKING_NODES_DELEGATOR_ROLE - ", vm.toString(address(actors.admin.STAKING_NODES_DELEGATOR))); + + // PAUSER_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.PAUSER_ROLE(), + address(actors.ops.PAUSE_ADMIN) + ), + "stakingNodesManager: PAUSE_ADMIN INVALID" + ); + console.log("\u2705 stakingNodesManager: PAUSE_ADMIN - ", vm.toString(address(actors.ops.PAUSE_ADMIN))); + + // UNPAUSER_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.UNPAUSER_ROLE(), + address(actors.admin.UNPAUSE_ADMIN) + ), + "stakingNodesManager: UNPAUSE_ADMIN INVALID" + ); + console.log("\u2705 stakingNodesManager: UNPAUSE_ADMIN - ", vm.toString(address(actors.admin.UNPAUSE_ADMIN))); + + + // STAKING_NODES_WITHDRAWER_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.STAKING_NODES_WITHDRAWER_ROLE(), + address(deployment.withdrawalsProcessor) + ), + "stakingNodesManager: STAKING_NODES_WITHDRAWER_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: STAKING_NODES_WITHDRAWER_ROLE - ", vm.toString(address(deployment.withdrawalsProcessor))); + + // WITHDRAWAL_MANAGER_ROLE + require( + deployment.stakingNodesManager.hasRole( + deployment.stakingNodesManager.WITHDRAWAL_MANAGER_ROLE(), + address(deployment.withdrawalsProcessor) + ), + "stakingNodesManager: WITHDRAWAL_MANAGER_ROLE INVALID" + ); + console.log("\u2705 stakingNodesManager: WITHDRAWAL_MANAGER_ROLE - ", vm.toString(address(deployment.withdrawalsProcessor))); + + //-------------------------------------------------------------------------------------- + //-------------------------------- ynETH roles --------------------------------------- + //-------------------------------------------------------------------------------------- + + // DEFAULT_ADMIN_ROLE + require( + deployment.ynETH.hasRole( + deployment.ynETH.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "ynETH: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 ynETH: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // PAUSER_ROLE; + require( + deployment.ynETH.hasRole( + deployment.ynETH.PAUSER_ROLE(), + address(actors.ops.PAUSE_ADMIN) + ), + "ynETH: PAUSER_ADMIN_ROLE INVALID" + ); + console.log("\u2705 ynETH: PAUSER_ROLE - ", vm.toString(address(actors.ops.PAUSE_ADMIN))); + + // UNPAUSER_ROLE; + require( + deployment.ynETH.hasRole( + deployment.ynETH.UNPAUSER_ROLE(), + address(actors.admin.UNPAUSE_ADMIN) + ), + "ynETH: UNPAUSER_ADMIN_ROLE INVALID" + ); + console.log("\u2705 ynETH: UNPAUSER_ROLE - ", vm.toString(address(actors.admin.UNPAUSE_ADMIN))); + + + // BURNER_ROLE; + require( + deployment.ynETH.hasRole( + deployment.ynETH.BURNER_ROLE(), + address(deployment.withdrawalQueueManager) + ), + "ynETH: BURNER_ROLE INVALID" + ); + console.log("\u2705 ynETH: BURNER_ROLE - ", vm.toString(address(deployment.withdrawalQueueManager))); + + //-------------------------------------------------------------------------------------- + //------------------ withdrawalQueueManager roles ------------------------------------ + //-------------------------------------------------------------------------------------- + + // DEFAULT_ADMIN_ROLE + require( + deployment.withdrawalQueueManager.hasRole( + deployment.withdrawalQueueManager.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "withdrawalQueueManager: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 withdrawalQueueManager: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // WITHDRAWAL_QUEUE_ADMIN_ROLE + require( + deployment.withdrawalQueueManager.hasRole( + deployment.withdrawalQueueManager.WITHDRAWAL_QUEUE_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "withdrawalQueueManager: WITHDRAWAL_QUEUE_ADMIN_ROLE INVALID" + ); + console.log("\u2705 withdrawalQueueManager: WITHDRAWAL_QUEUE_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // REQUEST_FINALIZER_ROLE + require( + deployment.withdrawalQueueManager.hasRole( + deployment.withdrawalQueueManager.REQUEST_FINALIZER_ROLE(), + address(actors.ops.REQUEST_FINALIZER) + ), + "withdrawalQueueManager: REQUEST_FINALIZER_ROLE INVALID" + ); + console.log("\u2705 withdrawalQueueManager: REQUEST_FINALIZER_ROLE - ", vm.toString(address(actors.ops.REQUEST_FINALIZER))); + + // REDEMPTION_ASSET_WITHDRAWER_ROLE + require( + deployment.withdrawalQueueManager.hasRole( + deployment.withdrawalQueueManager.REDEMPTION_ASSET_WITHDRAWER_ROLE(), + address(actors.ops.REDEMPTION_ASSET_WITHDRAWER) + ), + "withdrawalQueueManager: REDEMPTION_ASSET_WITHDRAWER_ROLE INVALID" + ); + console.log("\u2705 withdrawalQueueManager: REDEMPTION_ASSET_WITHDRAWER_ROLE - ", vm.toString(address(actors.ops.REDEMPTION_ASSET_WITHDRAWER))); + + //-------------------------------------------------------------------------------------- + //------------------ ynETHRedemptionAssetsVault roles --------------------------------- + //-------------------------------------------------------------------------------------- + + // DEFAULT_ADMIN_ROLE + require( + deployment.ynETHRedemptionAssetsVaultInstance.hasRole( + deployment.ynETHRedemptionAssetsVaultInstance.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "ynETHRedemptionAssetsVault: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 ynETHRedemptionAssetsVault: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.PROXY_ADMIN_OWNER))); + + // PAUSER_ROLE + require( + deployment.ynETHRedemptionAssetsVaultInstance.hasRole( + deployment.ynETHRedemptionAssetsVaultInstance.PAUSER_ROLE(), + address(actors.admin.ADMIN) + ), + "ynETHRedemptionAssetsVault: PAUSER_ROLE INVALID" + ); + console.log("\u2705 ynETHRedemptionAssetsVault: PAUSER_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // UNPAUSER_ROLE + require( + deployment.ynETHRedemptionAssetsVaultInstance.hasRole( + deployment.ynETHRedemptionAssetsVaultInstance.UNPAUSER_ROLE(), + address(actors.admin.UNPAUSE_ADMIN) + ), + "ynETHRedemptionAssetsVault: UNPAUSER_ROLE INVALID" + ); + console.log("\u2705 ynETHRedemptionAssetsVault: UNPAUSER_ROLE - ", vm.toString(address(actors.admin.UNPAUSE_ADMIN))); + + // Verify redeemer + require( + deployment.ynETHRedemptionAssetsVaultInstance.redeemer() == address(deployment.withdrawalQueueManager), + "ynETHRedemptionAssetsVault: redeemer INVALID" + ); + console.log("\u2705 ynETHRedemptionAssetsVault: redeemer - ", vm.toString(address(deployment.withdrawalQueueManager))); + + + //-------------------------------------------------------------------------------------- + //------------------ WithdrawalsProcessor roles -------------------------------------- + //-------------------------------------------------------------------------------------- + + // DEFAULT_ADMIN_ROLE + require( + deployment.withdrawalsProcessor.hasRole( + deployment.withdrawalsProcessor.DEFAULT_ADMIN_ROLE(), + address(actors.admin.ADMIN) + ), + "WithdrawalsProcessor: DEFAULT_ADMIN_ROLE INVALID" + ); + console.log("\u2705 WithdrawalsProcessor: DEFAULT_ADMIN_ROLE - ", vm.toString(address(actors.admin.ADMIN))); + + // WITHDRAWAL_MANAGER_ROLE + require( + deployment.withdrawalsProcessor.hasRole( + deployment.withdrawalsProcessor.WITHDRAWAL_MANAGER_ROLE(), + address(actors.ops.WITHDRAWAL_MANAGER) + ), + "WithdrawalsProcessor: WITHDRAWAL_MANAGER_ROLE INVALID" + ); + console.log("\u2705 WithdrawalsProcessor: WITHDRAWAL_MANAGER_ROLE - ", vm.toString(address(actors.ops.WITHDRAWAL_MANAGER))); + + // Verify stakingNodesManager + require( + address(deployment.withdrawalsProcessor.stakingNodesManager()) == address(deployment.stakingNodesManager), + "WithdrawalsProcessor: stakingNodesManager INVALID" + ); + console.log("\u2705 WithdrawalsProcessor: stakingNodesManager - ", vm.toString(address(deployment.stakingNodesManager))); + + } + + function verifySystemParameters() internal view { + // Verify the system parameters + require( + deployment.rewardsDistributor.feesBasisPoints() == 1000, + "ynETH: feesBasisPoints INVALID" + ); + console.log("\u2705 ynETH: feesBasisPoints - Value:", deployment.rewardsDistributor.feesBasisPoints()); + + require( + deployment.ynETH.depositsPaused() == false, + "ynETH: depositsPaused INVALID" + ); + console.log("\u2705 ynETH: depositsPaused - Value:", deployment.ynETH.depositsPaused()); + + require( + deployment.stakingNodesManager.maxNodeCount() == 10, + "ynETH: maxNodeCount INVALID" + ); + console.log("\u2705 ynETH: maxNodeCount - Value:", deployment.stakingNodesManager.maxNodeCount()); + + require( + deployment.stakingNodesManager.validatorRegistrationPaused() == false, + "ynETH: validatorRegistrationPaused INVALID" + ); + console.log("\u2705 ynETH: validatorRegistrationPaused - Value:", deployment.stakingNodesManager.validatorRegistrationPaused()); + + + // EXPECTING 5 BIPS for holesky and 10 BPS for mainnet + require( + deployment.withdrawalQueueManager.withdrawalFee() == (block.chainid == 17000 ? 500 : 1000), + "WithdrawalQueueManager: withdrawalFee INVALID" + ); + console.log("\u2705 WithdrawalQueueManager: withdrawalFee - Value:", deployment.withdrawalQueueManager.withdrawalFee()); + + console.log("\u2705 All system parameters verified successfully"); + } + + function verifyContractDependencies() internal { + + verifyYnETHDependencies(); + verifyStakingNodesManagerDependencies(); + verifyRewardsDistributorDependencies(); + verifyAllStakingNodeDependencies(); + + + verifyWithdrawalQueueManagerDependencies(); + verifyYnETHRedemptionAssetsVaultDependencies(); + verifyWithdrawalsProcessorDependencies(); + + console.log("\u2705 All contract dependencies verified successfully"); + } + + function verifyWithdrawalsProcessorDependencies() internal view { + require( + address(deployment.withdrawalsProcessor.stakingNodesManager()) == address(deployment.stakingNodesManager), + "WithdrawalsProcessor: stakingNodesManager dependency mismatch" + ); + + console.log("\u2705 WithdrawalsProcessor dependencies verified successfully"); + } + + function verifyYnETHRedemptionAssetsVaultDependencies() internal view { + require( + address(deployment.ynETHRedemptionAssetsVaultInstance.ynETH()) == address(deployment.ynETH), + "YnETHRedemptionAssetsVault: ynETH dependency mismatch" + ); + require( + address(deployment.ynETHRedemptionAssetsVaultInstance.redeemer()) == address(deployment.withdrawalQueueManager), + "YnETHRedemptionAssetsVault: withdrawalQueueManager dependency mismatch" + ); + + console.log("\u2705 YnETHRedemptionAssetsVault dependencies verified successfully"); + } + + function verifyWithdrawalQueueManagerDependencies() internal view { + + require( + address(deployment.withdrawalQueueManager.redeemableAsset()) == address(deployment.ynETH), + "WithdrawalQueueManager: redeemableAsset dependency mismatch" + ); + require( + address(deployment.withdrawalQueueManager.redemptionAssetsVault()) == address(deployment.ynETHRedemptionAssetsVaultInstance), + "WithdrawalQueueManager: redemptionAssetsVault dependency mismatch" + ); + + console.log("\u2705 WithdrawalQueueManager dependencies verified successfully"); + } + + function verifyYnETHDependencies() internal view { + // Verify ynETH contract dependencies + require( + address(deployment.ynETH.rewardsDistributor()) == address(deployment.rewardsDistributor), + "ynETH: RewardsDistributor dependency mismatch" + ); + require( + address(deployment.ynETH.stakingNodesManager()) == address(deployment.stakingNodesManager), + "ynETH: StakingNodesManager dependency mismatch" + ); + + console.log("\u2705 ynETH dependencies verified successfully"); + } + + function verifyRewardsDistributorDependencies() internal view { + // Verify RewardsDistributor contract dependencies + require( + address(deployment.rewardsDistributor.ynETH()) == address(deployment.ynETH), + "RewardsDistributor: ynETH dependency mismatch" + ); + require( + address(deployment.rewardsDistributor.executionLayerReceiver()) == address(deployment.executionLayerReceiver), + "RewardsDistributor: executionLayerReceiver dependency mismatch" + ); + require( + address(deployment.rewardsDistributor.consensusLayerReceiver()) == address(deployment.consensusLayerReceiver), + "RewardsDistributor: consensusLayerReceiver dependency mismatch" + ); + + console.log("\u2705 RewardsDistributor dependencies verified"); + } + + function verifyStakingNodesManagerDependencies() internal view { + require( + address(deployment.stakingNodesManager.ynETH()) == address(deployment.ynETH), + "StakingNodesManager: ynETH dependency mismatch" + ); + + require( + address(deployment.stakingNodesManager.rewardsDistributor()) == address(deployment.rewardsDistributor), + "StakingNodesManager: rewardsDistributor dependency mismatch" + ); + + require( + address(deployment.stakingNodesManager.eigenPodManager()) == chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS, + "StakingNodesManager: eigenPodManager dependency mismatch" + ); + require( + address(deployment.stakingNodesManager.depositContractEth2()) == chainAddresses.ethereum.DEPOSIT_2_ADDRESS, + "StakingNodesManager: depositContractEth2 dependency mismatch" + ); + require( + address(deployment.stakingNodesManager.delegationManager()) == chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS, + "StakingNodesManager: delegationManager dependency mismatch" + ); + require( + address(deployment.stakingNodesManager.strategyManager()) == chainAddresses.eigenlayer.STRATEGY_MANAGER_ADDRESS, + "StakingNodesManager: strategyManager dependency mismatch" + ); + + require( + address(deployment.stakingNodesManager.upgradeableBeacon().implementation()) == address(deployment.stakingNodeImplementation), + "StakingNodesManager: upgradeableBeacon implementation mismatch" + ); + + require( + address(deployment.stakingNodesManager.redemptionAssetsVault()) == address(deployment.ynETHRedemptionAssetsVaultInstance), + "StakingNodesManager: redemptionAssetsVault dependency mismatch" + ); + console.log("\u2705 StakingNodesManager: redemptionAssetsVault dependency verified"); + + console.log("\u2705 StakingNodesManager dependencies verified"); + } + + function verifyAllStakingNodeDependencies() internal view { + IStakingNode[] memory stakingNodes = deployment.stakingNodesManager.getAllNodes(); + for (uint256 i = 0; i < stakingNodes.length; i++) { + IStakingNode stakingNode = stakingNodes[i]; + require( + address(stakingNode.stakingNodesManager()) == address(deployment.stakingNodesManager), + "StakingNode: StakingNodesManager dependency mismatch" + ); + address storedPod = address(IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS).ownerToPod(address(stakingNode))); + + console.log("StakingNode address:", address(stakingNode)); + console.log("EigenPod address:", address(stakingNode.eigenPod())); + assert( + address(stakingNode.eigenPod()) == storedPod + ); + console.log("\u2705 StakingNode dependencies verified for node", i); + } + } + + function veryifySanityChecks() internal view { + // Check that previewDeposit of 1 ETH is less than 1 ether + uint256 previewDepositResult = deployment.ynETH.previewDeposit(1 ether); + require(previewDepositResult < 1 ether, "previewDeposit of 1 ETH should be less than 1 ether"); + console.log("\u2705 previewDeposit of 1 ETH is less than 1 ether"); + + // Check that totalSupply is less than totalAssets + uint256 totalSupply = deployment.ynETH.totalSupply(); + uint256 totalAssets = deployment.ynETH.totalAssets(); + require(totalSupply < totalAssets, "totalSupply should be less than totalAssets"); + console.log("\u2705 totalSupply is less than totalAssets"); + + // Print totalSupply and totalAssets + console.log(string.concat("Total Supply: ", vm.toString(totalSupply), " ynETH (", vm.toString(totalSupply / 1e18), " units)")); + console.log(string.concat("Total Assets: ", vm.toString(totalAssets), " wei (", vm.toString(totalAssets / 1e18), " ETH)")); + + + uint256 previewRedeemResult = deployment.ynETH.previewRedeem(1 ether); + console.log(string.concat("previewRedeem of 1 ynETH: ", vm.toString(previewRedeemResult), " wei (", vm.toString(previewRedeemResult / 1e18), " ETH)")); + + // Check that ETH balance of ynETH + redemption assets vault + staking nodes equals totalAssets + uint256 ynETHBalance = address(deployment.ynETH).balance; + uint256 redemptionVaultBalance = address(deployment.ynETHRedemptionAssetsVaultInstance).balance; + + uint256 stakingNodesBalance = 0; + IStakingNode[] memory stakingNodes = deployment.stakingNodesManager.getAllNodes(); + for (uint256 i = 0; i < stakingNodes.length; i++) { + stakingNodesBalance += stakingNodes[i].getETHBalance(); + console.log(string.concat("Balance for node ", vm.toString(i), ": ", vm.toString(stakingNodes[i].getETHBalance()), " wei (", vm.toString(stakingNodes[i].getETHBalance() / 1e18), " ETH)")); + + } + + uint256 totalCalculatedBalance = ynETHBalance + redemptionVaultBalance + stakingNodesBalance; + require(totalCalculatedBalance == totalAssets, "Sum of balances should equal totalAssets"); + console.log("\u2705 Sum of ETH balances equals totalAssets"); + + // Assert the correct number of staking nodes for mainnet and holesky + uint256 expectedNodeCount = block.chainid == 1 ? 5 : (block.chainid == 17000 ? 3 : 0); + uint256 actualNodeCount = stakingNodes.length; + require( + actualNodeCount == expectedNodeCount, + string.concat( + "Incorrect number of staking nodes. Expected: ", + vm.toString(expectedNodeCount), + ", Actual: ", + vm.toString(actualNodeCount) + ) + ); + console.log( + string.concat( + "\u2705 Correct number of staking nodes: ", + vm.toString(actualNodeCount) + ) + ); + + // Check ynViewer getRate is greater than 1 ether + uint256 ynETHRate = deployment.ynViewer.getRate(); + require(ynETHRate > 1 ether, "ynETH rate should be greater than 1 ether"); + console.log(string.concat("\u2705 ynETH rate is greater than 1 ether: ", vm.toString(ynETHRate), " wei (", vm.toString(ynETHRate / 1e18), " ETH)")); + } +} \ No newline at end of file diff --git a/script/ynEigen/BaseYnEigenScript.s.sol b/script/ynEigen/BaseYnEigenScript.s.sol index 639222fd7..a7af630b8 100644 --- a/script/ynEigen/BaseYnEigenScript.s.sol +++ b/script/ynEigen/BaseYnEigenScript.s.sol @@ -21,6 +21,17 @@ import {console} from "lib/forge-std/src/console.sol"; contract BaseYnEigenScript is BaseScript { using stdJson for string; + + struct DeploymentProxies { + ProxyAddresses ynEigen; + ProxyAddresses assetRegistry; + ProxyAddresses eigenStrategyManager; + ProxyAddresses tokenStakingNodesManager; + ProxyAddresses ynEigenDepositAdapter; + ProxyAddresses rateProvider; + ProxyAddresses ynEigenViewer; + } + struct Deployment { ynEigen ynEigen; AssetRegistry assetRegistry; @@ -31,6 +42,7 @@ contract BaseYnEigenScript is BaseScript { IRateProvider rateProvider; TimelockController upgradeTimelock; ynEigenViewer viewer; + DeploymentProxies proxies; } struct Asset { @@ -133,21 +145,41 @@ contract BaseYnEigenScript is BaseScript { function loadDeployment() public view returns (Deployment memory) { string memory deploymentFile = getDeploymentFile(); string memory jsonContent = vm.readFile(deploymentFile); + Deployment memory deployment; + DeploymentProxies memory proxies; + deployment.ynEigen = ynEigen(payable(jsonContent.readAddress(string.concat(".proxy-", tokenName())))); + proxies.ynEigen = loadProxyAddresses(jsonContent, tokenName()); + deployment.tokenStakingNodesManager = TokenStakingNodesManager(payable(jsonContent.readAddress(".proxy-tokenStakingNodesManager"))); + proxies.tokenStakingNodesManager = loadProxyAddresses(jsonContent, "tokenStakingNodesManager"); + deployment.assetRegistry = AssetRegistry(payable(jsonContent.readAddress(".proxy-assetRegistry"))); + proxies.assetRegistry = loadProxyAddresses(jsonContent, "assetRegistry"); + deployment.eigenStrategyManager = EigenStrategyManager(payable(jsonContent.readAddress(".proxy-eigenStrategyManager"))); + proxies.eigenStrategyManager = loadProxyAddresses(jsonContent, "eigenStrategyManager"); + deployment.tokenStakingNodeImplementation = TokenStakingNode(payable(jsonContent.readAddress(".tokenStakingNodeImplementation"))); + deployment.ynEigenDepositAdapterInstance = ynEigenDepositAdapter(payable(jsonContent.readAddress(".proxy-ynEigenDepositAdapter"))); + proxies.ynEigenDepositAdapter = loadProxyAddresses(jsonContent, "ynEigenDepositAdapter"); + deployment.rateProvider = IRateProvider(payable(jsonContent.readAddress(".proxy-rateProvider"))); + proxies.rateProvider = loadProxyAddresses(jsonContent, "rateProvider"); + deployment.viewer = ynEigenViewer(payable(jsonContent.readAddress(".proxy-ynEigenViewer"))); + proxies.ynEigenViewer = loadProxyAddresses(jsonContent, "ynEigenViewer"); + deployment.upgradeTimelock = TimelockController(payable(jsonContent.readAddress(".upgradeTimelock"))); + deployment.proxies = proxies; + return deployment; } diff --git a/script/ynEigen/YnEigenDeployer.s.sol b/script/ynEigen/YnEigenDeployer.s.sol index 042657d6c..445ebbdd8 100644 --- a/script/ynEigen/YnEigenDeployer.s.sol +++ b/script/ynEigen/YnEigenDeployer.s.sol @@ -2,6 +2,10 @@ pragma solidity ^0.8.24; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + + import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; @@ -166,6 +170,11 @@ contract YnEigenDeployer is BaseYnEigenScript { vm.stopBroadcast(); + ProxyAddresses memory proxyAddressesEmpty = ProxyAddresses( + TransparentUpgradeableProxy(payable(address(0))), + ProxyAdmin(address(0)), + address(0) + ); Deployment memory deployment = Deployment({ ynEigen: ynToken, assetRegistry: assetRegistry, @@ -175,7 +184,16 @@ contract YnEigenDeployer is BaseYnEigenScript { ynEigenDepositAdapterInstance: ynEigenDepositAdapterInstance, rateProvider: rateProvider, upgradeTimelock: timelock, - viewer: viewer + viewer: viewer, + proxies: DeploymentProxies({ + ynEigen: proxyAddressesEmpty, + assetRegistry: proxyAddressesEmpty, + eigenStrategyManager: proxyAddressesEmpty, + tokenStakingNodesManager: proxyAddressesEmpty, + rateProvider: proxyAddressesEmpty, + ynEigenDepositAdapter: proxyAddressesEmpty, + ynEigenViewer: proxyAddressesEmpty + }) }); saveDeployment(deployment); diff --git a/script/ynEigen/YnEigenVerifier.s.sol b/script/ynEigen/YnEigenVerifier.s.sol index 44bb5eca2..900f736ca 100644 --- a/script/ynEigen/YnEigenVerifier.s.sol +++ b/script/ynEigen/YnEigenVerifier.s.sol @@ -26,6 +26,7 @@ contract YnEigenVerifier is BaseYnEigenScript { deployment = loadDeployment(); verifyUpgradeTimelockRoles(); + verifyProxies(); verifyProxyAdminOwners(); verifyRoles(); verifySystemParameters(); @@ -78,6 +79,84 @@ contract YnEigenVerifier is BaseYnEigenScript { console.log("\u2705 upgradeTimelock: DELAY - ", deployment.upgradeTimelock.getMinDelay()); } + function verifyProxyContract( + address contractAddress, + string memory contractName, + ProxyAddresses memory proxyAddresses + ) internal view { + + address expectedProxyAdminOwner; + + // TODO: consider changing owner here for consistency + if (keccak256(abi.encodePacked(contractName)) == keccak256(abi.encodePacked("ynEigenViewer")) && block.chainid == 1) { + expectedProxyAdminOwner = actors.admin.PROXY_ADMIN_OWNER; + } else { + expectedProxyAdminOwner = address(deployment.upgradeTimelock); + } + + // Verify PROXY_ADMIN_OWNER + address proxyAdminAddress = Utils.getTransparentUpgradeableProxyAdminAddress(contractAddress); + address proxyAdminOwner = ProxyAdmin(proxyAdminAddress).owner(); + require( + proxyAdminOwner == expectedProxyAdminOwner, + string.concat(contractName, ": PROXY_ADMIN_OWNER mismatch, expected: ", vm.toString(expectedProxyAdminOwner), ", got: ", vm.toString(proxyAdminOwner)) + ); + console.log(string.concat("\u2705 ", contractName, ": PROXY_ADMIN_OWNER - ", vm.toString(proxyAdminOwner))); + + // Verify ProxyAdmin address + require( + proxyAdminAddress == address(proxyAddresses.proxyAdmin), + string.concat(contractName, ": ProxyAdmin address mismatch, expected: ", vm.toString(address(proxyAddresses.proxyAdmin)), ", got: ", vm.toString(proxyAdminAddress)) + ); + console.log(string.concat("\u2705 ", contractName, ": ProxyAdmin address - ", vm.toString(proxyAdminAddress))); + + // Verify Implementation address + address implementationAddress = Utils.getTransparentUpgradeableProxyImplementationAddress(contractAddress); + require( + implementationAddress == proxyAddresses.implementation, + string.concat(contractName, ": Implementation address mismatch, expected: ", vm.toString(proxyAddresses.implementation), ", got: ", vm.toString(implementationAddress)) + ); + console.log(string.concat("\u2705 ", contractName, ": Implementation address - ", vm.toString(implementationAddress))); + } + + function verifyProxies() internal view { + verifyProxyContract( + address(deployment.ynEigen), + "ynEigen", + deployment.proxies.ynEigen + ); + + verifyProxyContract( + address(deployment.assetRegistry), + "assetRegistry", + deployment.proxies.assetRegistry + ); + + verifyProxyContract( + address(deployment.eigenStrategyManager), + "eigenStrategyManager", + deployment.proxies.eigenStrategyManager + ); + + verifyProxyContract( + address(deployment.tokenStakingNodesManager), + "tokenStakingNodesManager", + deployment.proxies.tokenStakingNodesManager + ); + + verifyProxyContract( + address(deployment.ynEigenDepositAdapterInstance), + "ynEigenDepositAdapter", + deployment.proxies.ynEigenDepositAdapter + ); + + verifyProxyContract( + address(deployment.viewer), + "ynEigenViewer", + deployment.proxies.ynEigenViewer + ); + } + function verifyProxyAdminOwners() internal view { address proxyAdminOwner = address(deployment.upgradeTimelock); diff --git a/script/ynEigen/input/lsd-holesky.json b/script/ynEigen/input/lsd-holesky.json index 93c3c34e3..160b0d94d 100644 --- a/script/ynEigen/input/lsd-holesky.json +++ b/script/ynEigen/input/lsd-holesky.json @@ -1,7 +1,7 @@ { "chainId": 17000, "name": "YieldNest Restaked LSD - Eigenlayer", - "symbol": "YnLSDe", + "symbol": "ynLSDe", "assets": [ { "name": "WSTETH", diff --git a/script/ynEigen/input/lsd-mainnet.json b/script/ynEigen/input/lsd-mainnet.json index 19b945f71..a5aacacf4 100644 --- a/script/ynEigen/input/lsd-mainnet.json +++ b/script/ynEigen/input/lsd-mainnet.json @@ -19,9 +19,9 @@ "strategy": "0xa4C637e0F704745D182e4D38cAb7E7485321d059" }, { - "name": "RETH", - "token": "0xae78736Cd615f374D3085123A210448E74Fc6393", - "strategy": "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2" + "name": "METH", + "token": "0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa", + "strategy": "0x298aFB19A105D59E74658C4C334Ff360BadE6dd2" } ] } diff --git a/src/Constants.sol b/src/Constants.sol new file mode 100644 index 000000000..c3383bb23 --- /dev/null +++ b/src/Constants.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; + +address constant ETH_ASSET = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; +uint256 constant YNETH_UNIT = 1e18; +uint256 constant ONE_GWEI = 1e9; +IStrategy constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); +uint256 constant DEFAULT_VALIDATOR_STAKE = 32 ether; \ No newline at end of file diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 7cd030b45..3bb3478e1 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -125,6 +125,8 @@ contract RewardsDistributor is AccessControlUpgradeable, RewardsDistributorEvent uint256 netRewards = totalRewards - fees; if (netRewards > 0) { + // NOTE: Having the permisionless processRewards able to send rewards to ynETH + // opens up ynETH to donation attack for a non boostrapped system. ynETH.receiveRewards{value: netRewards}(); } @@ -143,8 +145,10 @@ contract RewardsDistributor is AccessControlUpgradeable, RewardsDistributorEvent //---------------------------------- SETTERS ----------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Sets the fees receiver wallet for the protocol. - /// @param newReceiver The new fees receiver wallet. + /** + * @notice Sets the fees receiver wallet for the protocol. + * @param newReceiver The new fees receiver wallet. + */ function setFeesReceiver(address payable newReceiver) external onlyRole(REWARDS_ADMIN_ROLE) @@ -154,8 +158,10 @@ contract RewardsDistributor is AccessControlUpgradeable, RewardsDistributorEvent emit FeeReceiverSet(newReceiver); } - /// @notice Sets the fees basis points for the protocol. - /// @param newFeesBasisPoints The new fees basis points. + /** + * @notice Sets the fees basis points for the protocol. + * @param newFeesBasisPoints The new fees basis points. + */ function setFeesBasisPoints(uint16 newFeesBasisPoints) external onlyRole(REWARDS_ADMIN_ROLE) @@ -165,18 +171,19 @@ contract RewardsDistributor is AccessControlUpgradeable, RewardsDistributorEvent emit FeesBasisPointsSet(newFeesBasisPoints); } + /** + * @notice Ensures that the contract's balance remains unchanged after the execution of a function. + */ modifier assertBalanceUnchanged() { uint256 before = address(this).balance; _; assert(address(this).balance == before); } - //-------------------------------------------------------------------------------------- - //---------------------------------- MODIFIERS --------------------------------------- - //-------------------------------------------------------------------------------------- - - /// @notice Ensure that the given address is not the zero address. - /// @param _address The address to check. + /** + * @notice Ensures that the given address is not the zero address. + * @param _address The address to check. + */ modifier notZeroAddress(address _address) { if (_address == address(0)) { revert ZeroAddress(); diff --git a/src/RewardsReceiver.sol b/src/RewardsReceiver.sol index 479945909..9c1a3a114 100644 --- a/src/RewardsReceiver.sol +++ b/src/RewardsReceiver.sol @@ -12,9 +12,11 @@ interface IRewardsReceiverEvents { event EthWithdrawn(address indexed to, uint256 amount); } -/// @title ReturnsReceiver -/// @notice Receives protocol level returns and manages who can withdraw the returns. Deployed as the -/// consensus layer withdrawal wallet and execution layer rewards wallet in the protocol. +/** + * @title ReturnsReceiver + * @notice Receives protocol level returns and manages who can withdraw the returns. Deployed as the + * consensus layer withdrawal wallet and execution layer rewards wallet in the protocol. + */ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsReceiverEvents { //-------------------------------------------------------------------------------------- @@ -29,7 +31,9 @@ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsRec //---------------------------------- ROLES ------------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice The withdrawer role can withdraw ETH and ERC20 assets from this contract. + /** + * @notice The withdrawer role can withdraw ETH and ERC20 assets from this contract. + */ bytes32 public constant WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE"); //-------------------------------------------------------------------------------------- @@ -40,13 +44,17 @@ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsRec _disableInitializers(); } - /// @notice Configuration for contract initialization. + /** + * @notice Configuration for contract initialization. + */ struct Init { address admin; address withdrawer; } - /// @notice Inititalizes the contract. + /** + * @notice Initializes the contract. + */ function initialize(Init memory init) external notZeroAddress(init.admin) @@ -64,8 +72,10 @@ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsRec //---------------------------------- TRANSFERS --------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Transfers the given amount of ETH to an address. - /// @dev Only called by the withdrawer. + /** + * @notice Transfers the given amount of ETH to an address. + * @dev Only called by the withdrawer. + */ function transferETH(address payable to, uint256 amount) external onlyRole(WITHDRAWER_ROLE) { if (address(this).balance < amount) { revert InsufficientBalance(); @@ -78,8 +88,10 @@ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsRec emit EthWithdrawn(to, amount); } - /// @notice Transfers the given amount of an ERC20 asset to an address. - /// @dev Only called by the withdrawer. + /** + * @notice Transfers the given amount of an ERC20 asset to an address. + * @dev Only called by the withdrawer. + */ function transferERC20(IERC20 asset, address to, uint256 amount) external onlyRole(WITHDRAWER_ROLE) { SafeERC20.safeTransfer(asset, to, amount); emit AssetWithdrawn(address(asset), to, amount); @@ -89,8 +101,10 @@ contract RewardsReceiver is Initializable, AccessControlUpgradeable, IRewardsRec //---------------------------------- MODIFIERS --------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Ensure that the given address is not the zero address. - /// @param _address The address to check. + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ modifier notZeroAddress(address _address) { if (_address == address(0)) { revert ZeroAddress(); diff --git a/src/StakingNode.sol b/src/StakingNode.sol index 6b2cecbef..6a594a61a 100644 --- a/src/StakingNode.sol +++ b/src/StakingNode.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {ReentrancyGuardUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {BeaconChainProofs} from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; import {IDelegationManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IEigenPodManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IEigenPod } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; import {ISignatureUtils} from "lib/eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; @@ -11,8 +12,8 @@ import {IBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; import {IStakingNode} from "src/interfaces/IStakingNode.sol"; -import {RewardsType} from "src/interfaces/IRewardsDistributor.sol"; - +import {IERC20} from "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol"; +import {DEFAULT_VALIDATOR_STAKE} from "src/Constants.sol"; interface StakingNodeEvents { event EigenPodCreated(address indexed nodeAddress, address indexed podAddress); @@ -21,14 +22,20 @@ interface StakingNodeEvents { event NonBeaconChainETHWithdrawalsProcessed(uint256 claimedAmount); event ETHReceived(address sender, uint256 value); event WithdrawnNonBeaconChainETH(uint256 amount, uint256 remainingBalance); - event AllocatedStakedETH(uint256 currenAllocatedStakedETH, uint256 newAmount); + event AllocatedStakedETH(uint256 currentUnverifiedStakedETH, uint256 newAmount); + event DeallocatedStakedETH(uint256 amount, uint256 currentWithdrawnETH); event ValidatorRestaked(uint40 indexed validatorIndex, uint64 oracleTimestamp, uint256 effectiveBalanceGwei); + event VerifyWithdrawalCredentialsCompleted(uint40 indexed validatorIndex, uint64 oracleTimestamp, uint256 effectiveBalanceGwei); event WithdrawalProcessed( uint40 indexed validatorIndex, - uint256 amount, + uint256 effectiveBalance, bytes32 withdrawalCredentials, + uint256 withdrawalAmount, uint64 oracleTimestamp ); + + event QueuedWithdrawals(uint256 sharesAmount, bytes32[] fullWithdrawalRoots); + event CompletedQueuedWithdrawals(IDelegationManager.Withdrawal[] withdrawals, uint256 totalWithdrawalAmount, uint256 actualWithdrawalAmount); } /** @@ -43,13 +50,17 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea //---------------------------------- ERRORS ------------------------------------------ //-------------------------------------------------------------------------------------- - error NotStakingNodesAdmin(); - error ETHDepositorNotDelayedWithdrawalRouter(); + error NotStakingNodesOperator(); + error ETHDepositorNotEigenPod(); error ClaimAmountTooLow(uint256 expected, uint256 actual); error ZeroAddress(); error NotStakingNodesManager(); error NotStakingNodesDelegator(); error NoBalanceToProcess(); + error MismatchInExpectedETHBalanceAfterWithdrawals(uint256 actualWithdrawalAmount, uint256 totalWithdrawalAmount); + error TransferFailed(); + error InsufficientWithdrawnETH(uint256 amount, uint256 withdrawnETH); + error NotStakingNodesWithdrawer(); //-------------------------------------------------------------------------------------- //---------------------------------- CONSTANTS --------------------------------------- @@ -65,13 +76,31 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea IEigenPod public eigenPod; uint256 public nodeId; - /// @dev Monitors the ETH balance that was committed to validators allocated to this StakingNode - uint256 public allocatedETH; + /** @dev Monitors the ETH balance that was committed to validators allocated to this StakingNode */ + uint256 private _unused_former_allocatedETH; + + /** @dev Accounts for withdrawn ETH balance that can be withdrawn by the StakingNodesManager contract + This is made up of both validator principal, rewards and arbitrary ETH sent to the Eigenpod + that are withdrawn from Eigenlayer. + */ + uint256 public withdrawnETH; + + /** + * @dev Accounts for ETH staked with validators whose withdrawal address is this Node's eigenPod. + * that is not yet verified with verifyWithdrawalCredentials. + * Increases when calling allocateETH, and decreases when verifying with verifyWithdrawalCredentials + */ + uint256 public unverifiedStakedETH; + /** + * @dev Amount of shares queued for withdrawal (no longer active in staking). 1 share == 1 ETH. + * Increases when calling queueWithdrawals, and decreases when calling completeQueuedWithdrawals. + */ + uint256 public queuedSharesAmount; - /// @dev Allows only a whitelisted address to configure the contract - modifier onlyAdmin() { - if(!stakingNodesManager.isStakingNodesOperator(msg.sender)) revert NotStakingNodesAdmin(); + /** @dev Allows only a whitelisted address to configure the contract */ + modifier onlyOperator() { + if(!stakingNodesManager.isStakingNodesOperator(msg.sender)) revert NotStakingNodesOperator(); _; } @@ -85,16 +114,21 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea _; } + modifier onlyStakingNodesWithdrawer() { + if (!stakingNodesManager.isStakingNodesWithdrawer(msg.sender)) revert NotStakingNodesWithdrawer(); + _; + } + //-------------------------------------------------------------------------------------- //---------------------------------- INITIALIZATION ---------------------------------- //-------------------------------------------------------------------------------------- receive() external payable { // Consensus Layer rewards and the validator principal will be sent this way. - if (msg.sender != address(stakingNodesManager.delayedWithdrawalRouter())) { - revert ETHDepositorNotDelayedWithdrawalRouter(); - } - emit ETHReceived(msg.sender, msg.value); + if (msg.sender != address(eigenPod)) { + revert ETHDepositorNotEigenPod(); + } + emit ETHReceived(msg.sender, msg.value); } constructor() { @@ -111,6 +145,10 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea nodeId = init.nodeId; } + function initializeV2(uint256 initialUnverifiedStakedETH) external onlyStakingNodesManager reinitializer(2) { + unverifiedStakedETH = initialUnverifiedStakedETH; + } + //-------------------------------------------------------------------------------------- //---------------------------------- EIGENPOD CREATION ------------------------------ //-------------------------------------------------------------------------------------- @@ -132,59 +170,31 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea } //-------------------------------------------------------------------------------------- - //---------------------------------- EXPEDITED WITHDRAWAL --------------------------- + //---------------------------------- VERIFICATION AND DELEGATION -------------------- //-------------------------------------------------------------------------------------- - - /** - * @notice Allows the StakingNode to withdraw ETH from the EigenPod before restaking. - * @dev This allows StakingNode to retrieve rewards from the Consensus Layer that accrue over time as - * validators sweep them to the withdrawal address - */ - function withdrawNonBeaconChainETHBalanceWei() external onlyAdmin { - - // withdraw all available balance to withdraw. - //Warning: the ETH balance of the EigenPod may be higher in case there's beacon chain ETH there - uint256 balanceToWithdraw = eigenPod.nonBeaconChainETHBalanceWei(); - eigenPod.withdrawNonBeaconChainETHBalanceWei(address(this), balanceToWithdraw); - emit WithdrawnNonBeaconChainETH(balanceToWithdraw, address(eigenPod).balance); - } - + /** - * @notice Processes withdrawals by verifying the node's balance and transferring ETH to the StakingNodesManager. - * @dev This function checks if the node's current balance matches the expected balance and then transfers the ETH to the StakingNodesManager. + * @dev Validates the withdrawal credentials for a withdrawal. + * This activates the staked funds within EigenLayer as shares. + * verifyWithdrawalCredentials MUST be called for all validators BEFORE they + * are exited from the beacon chain to keep the getETHBalance return value consistent. + * If a validator is exited without this call, TVL is double counted for its principal. + * @param beaconTimestamp The timestamp of the oracle that signed the block. + * @param stateRootProof The state root proof. + * @param validatorIndices The indices of the validators. + * @param validatorFieldsProofs The validator fields proofs. + * @param validatorFields The validator fields. */ - function processDelayedWithdrawals() public nonReentrant onlyAdmin { - - uint256 balance = address(this).balance; - if (balance == 0) { - revert NoBalanceToProcess(); - } - stakingNodesManager.processRewards{value: balance}(nodeId, RewardsType.ConsensusLayer); - emit NonBeaconChainETHWithdrawalsProcessed(balance); - } - - - //-------------------------------------------------------------------------------------- - //---------------------------------- VERIFICATION AND DELEGATION -------------------- - //-------------------------------------------------------------------------------------- - - /// @dev Validates the withdrawal credentials for a withdrawal - /// This activates the activation of the staked funds within EigenLayer - /// @param oracleTimestamp The timestamp of the oracle that signed the block - /// @param stateRootProof The state root proof - /// @param validatorIndices The indices of the validators - /// @param validatorFieldsProofs The validator fields proofs - /// @param validatorFields The validator fields function verifyWithdrawalCredentials( - uint64 oracleTimestamp, + uint64 beaconTimestamp, BeaconChainProofs.StateRootProof calldata stateRootProof, uint40[] calldata validatorIndices, bytes[] calldata validatorFieldsProofs, bytes32[][] calldata validatorFields - ) external onlyAdmin { + ) external onlyOperator { IEigenPod(address(eigenPod)).verifyWithdrawalCredentials( - oracleTimestamp, + beaconTimestamp, stateRootProof, validatorIndices, validatorFieldsProofs, @@ -192,20 +202,49 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea ); for (uint256 i = 0; i < validatorIndices.length; i++) { + // If the validator is already exited, the effectiveBalanceGwei is 0. + // if the validator has not been exited, the effectiveBalanceGwei is whatever is staked + // (32ETH in the absence of slasing, and less than that if slashed) uint256 effectiveBalanceGwei = validatorFields[i].getEffectiveBalanceGwei(); - emit ValidatorRestaked(validatorIndices[i], oracleTimestamp, effectiveBalanceGwei); + + emit VerifyWithdrawalCredentialsCompleted(validatorIndices[i], beaconTimestamp, effectiveBalanceGwei); + + // If the effectiveBalanceGwei is not 0, then the full stake of the validator + // is verified as part of this process and shares are credited to this StakingNode instance. + // verifyWithdrawalCredentials can only be called for non-exited validators + // This assumes StakingNodesManager.sol always stakes the full 32 ETH in one go. + // effectiveBalanceGwei *may* be less than DEFAULT_VALIDATOR_STAKE if the validator was slashed. + unverifiedStakedETH -= DEFAULT_VALIDATOR_STAKE; + + emit ValidatorRestaked(validatorIndices[i], beaconTimestamp, effectiveBalanceGwei); } } + /** + * @dev Create a checkpoint used to prove the pod's active validator set. + * This function can only be called by the Operator. + * @param revertIfNoBalance Forces a revert if the pod ETH balance is 0. + */ + function startCheckpoint(bool revertIfNoBalance) external onlyOperator { + eigenPod.startCheckpoint(revertIfNoBalance); + } + //-------------------------------------------------------------------------------------- //---------------------------------- DELEGATION ------------------------------------- //-------------------------------------------------------------------------------------- + /** + * @notice Delegates authority to an operator. + * @dev Delegates the staking node's authority to an operator using a signature with expiry. + * @param operator The address of the operator to whom the delegation is made. + * @param approverSignatureAndExpiry The signature of the approver along with its expiry details. + * @param approverSalt The unique salt used to prevent replay attacks. + */ function delegate( address operator, ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry, bytes32 approverSalt - ) public override onlyDelegator { + ) public override onlyDelegator { IDelegationManager delegationManager = IDelegationManager(address(stakingNodesManager.delegationManager())); delegationManager.delegateTo(operator, approverSignatureAndExpiry, approverSalt); @@ -213,6 +252,11 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea emit Delegated(operator, approverSalt); } + /** + * @notice Undelegates the authority previously delegated to an operator. + * @dev This function revokes the delegation by calling the `undelegate` method on the `DelegationManager`. + * It emits an `Undelegated` event with the address of the operator from whom the delegation is being removed. + */ function undelegate() public onlyDelegator { IDelegationManager delegationManager = IDelegationManager(address(stakingNodesManager.delegationManager())); @@ -228,52 +272,185 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea //---------------------------------- WITHDRAWALS ------------------------------------- //-------------------------------------------------------------------------------------- + /** + * @dev Queues a validator Principal withdrawal for processing. DelegationManager calls EigenPodManager.decreasesShares + * which decreases the `podOwner`'s shares by `shares`, down to a minimum of zero. + * @param sharesAmount The amount of shares to be queued for withdrawals. + * @return fullWithdrawalRoots An array of keccak256 hashes of each withdrawal created. + */ + function queueWithdrawals( + uint256 sharesAmount + ) external onlyStakingNodesWithdrawer returns (bytes32[] memory fullWithdrawalRoots) { - function verifyAndProcessWithdrawals( - uint64 oracleTimestamp, - BeaconChainProofs.StateRootProof calldata stateRootProof, - BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs, - bytes[] calldata validatorFieldsProofs, - bytes32[][] calldata validatorFields, - bytes32[][] calldata withdrawalFields - ) external onlyAdmin { + IDelegationManager delegationManager = IDelegationManager(address(stakingNodesManager.delegationManager())); - eigenPod.verifyAndProcessWithdrawals( - oracleTimestamp, - stateRootProof, - withdrawalProofs, - validatorFieldsProofs, - validatorFields, - withdrawalFields - ); + IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1); + IStrategy[] memory strategies = new IStrategy[](1); + + // Assumption: 1 Share of beaconChainETHStrategy = 1 ETH. + uint256[] memory shares = new uint256[](1); - for (uint256 i = 0; i < withdrawalProofs.length; i++) { - emit WithdrawalProcessed( - validatorFields[i].getValidatorIndex(), - validatorFields[i].getEffectiveBalanceGwei(), // Assuming the first field in withdrawalFields array is the amount - validatorFields[i].getWithdrawalCredentials(), - oracleTimestamp - ); + strategies[0] = beaconChainETHStrategy; + shares[0] = sharesAmount; + // The delegationManager requires the withdrawer == msg.sender (the StakingNode in this case). + params[0] = IDelegationManager.QueuedWithdrawalParams({ + strategies: strategies, + shares: shares, + withdrawer: address(this) + }); + + fullWithdrawalRoots = delegationManager.queueWithdrawals(params); + + // After running queueWithdrawals, eigenPodManager.podOwnerShares(address(this)) decreases by `sharesAmount`. + // Therefore queuedSharesAmount increase by `sharesAmount`. + + queuedSharesAmount += sharesAmount; + emit QueuedWithdrawals(sharesAmount, fullWithdrawalRoots); + } + + /** + * @dev Triggers the completion of particular queued withdrawals. + * Withdrawals can only be completed if + * max(delegationManager.minWithdrawalDelayBlocks(), delegationManager.strategyWithdrawalDelayBlocks(beaconChainETHStrategy)) + * number of blocks have passed since withdrawal was queued. + * @param withdrawals The Withdrawals to complete. This withdrawalRoot (keccak hash of the Withdrawal) must match the + * the withdrawal created as part of the queueWithdrawals call. + * @param middlewareTimesIndexes The middlewareTimesIndex parameter has to do + * with the Slasher, which currently does nothing. As of M2, this parameter + * has no bearing on anything and can be ignored + */ + function completeQueuedWithdrawals( + IDelegationManager.Withdrawal[] memory withdrawals, + uint256[] memory middlewareTimesIndexes + ) external onlyStakingNodesWithdrawer { + + uint256 totalWithdrawalAmount = 0; + + bool[] memory receiveAsTokens = new bool[](withdrawals.length); + IERC20[][] memory tokens = new IERC20[][](withdrawals.length); + for (uint256 i = 0; i < withdrawals.length; i++) { + + // Set receiveAsTokens to true to receive ETH when completeQueuedWithdrawals runs. + ///IMPORTANT: beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` + // and `withdrawal.withdrawer != withdrawal.staker`, any beaconChainETHStrategy shares + // in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, + // unlike shares in any other strategies, which will be transferred to the withdrawer. + receiveAsTokens[i] = true; + + // tokens array must match length of the withdrawals[i].strategies + // but does not need actual values in the case of the beaconChainETHStrategy + tokens[i] = new IERC20[](withdrawals[i].strategies.length); + + for (uint256 j = 0; j < withdrawals[i].shares.length; j++) { + totalWithdrawalAmount += withdrawals[i].shares[j]; + } } + + IDelegationManager delegationManager = IDelegationManager(address(stakingNodesManager.delegationManager())); + + uint256 initialETHBalance = address(this).balance; + + // NOTE: completeQueuedWithdrawals can only be called by withdrawal.withdrawer for each withdrawal + // The Eigenlayer beaconChainETHStrategy queued withdrawal completion flow follows the following steps: + // 1. The flow starts in the DelegationManager where queued withdrawals are managed. + // 2. For beaconChainETHStrategy, the DelegationManager calls _withdrawSharesAsTokens interacts with the EigenPodManager.withdrawSharesAsTokens + // 3. Finally, the EigenPodManager calls withdrawRestakedBeaconChainETH on the EigenPod of this StakingNode to finalize the withdrawal. + // 4. the EigenPod decrements withdrawableRestakedExecutionLayerGwei and send the ETH to address(this) + delegationManager.completeQueuedWithdrawals(withdrawals, tokens, middlewareTimesIndexes, receiveAsTokens); + + uint256 finalETHBalance = address(this).balance; + uint256 actualWithdrawalAmount = finalETHBalance - initialETHBalance; + + // NOTE: actualWithdrawalAmount may be < totalWithdrawalAmount in case of slashing ! + + // Shares are no longer queued; decrease what was queued for withdrawal + queuedSharesAmount -= totalWithdrawalAmount; + + // Withdraw validator principal resides in the StakingNode until StakingNodesManager retrieves it. + withdrawnETH += actualWithdrawalAmount; + + emit CompletedQueuedWithdrawals(withdrawals, totalWithdrawalAmount, actualWithdrawalAmount); } //-------------------------------------------------------------------------------------- //---------------------------------- ETH BALANCE ACCOUNTING -------------------------- //-------------------------------------------------------------------------------------- - /// @dev Record total staked ETH for this StakingNode + /** + * @dev Record total staked ETH for this StakingNode + */ function allocateStakedETH(uint256 amount) external payable onlyStakingNodesManager { - emit AllocatedStakedETH(allocatedETH, amount); - allocatedETH += amount; + emit AllocatedStakedETH(unverifiedStakedETH, amount); + + unverifiedStakedETH += amount; } + /** + * @notice Deallocates a specified amount of staked ETH from the withdrawn validator principal + * and transfers it to the StakingNodesManager. + * @dev This function can only be called by the StakingNodesManager. It emits a DeallocatedStakedETH + * event upon successful deallocation. + * @param amount The amount of ETH to deallocate and transfer. + */ + function deallocateStakedETH(uint256 amount) external payable onlyStakingNodesManager { + if (amount > withdrawnETH) { + revert InsufficientWithdrawnETH(amount, withdrawnETH); + } + + emit DeallocatedStakedETH(amount, withdrawnETH); + + withdrawnETH -= amount; + + (bool success, ) = address(stakingNodesManager).call{value: amount}(""); + if (!success) { + revert TransferFailed(); + } + } function getETHBalance() public view returns (uint256) { - // NOTE: when verifyWithdrawalCredentials is enabled - // the eigenpod will be credited with shares. Those shares represent 1 share = 1 ETH - // To get the shares call: strategyManager.stakerStrategyShares(address(this), beaconChainETHStrategy) - // This computation will need to be updated to factor in that. - return allocatedETH; + IEigenPodManager eigenPodManager = IEigenPodManager(IStakingNodesManager(stakingNodesManager).eigenPodManager()); + + // Compute the total ETH balance of the StakingNode + // This includes: + // 1. withdrawnETH: ETH that has been withdrawn from Eigenlayer and is held by this StakingNode + // 2. unverifiedStakedETH: ETH staked with validators but not yet verified + // 3. queuedSharesAmount: Shares queued for withdrawal (1 share = 1 ETH) + // 4. podOwnerShares: Active shares in Eigenlayer, representing staked ETH + int256 totalETHBalance = + int256(withdrawnETH + unverifiedStakedETH + queuedSharesAmount) + + eigenPodManager.podOwnerShares(address(this)); + + if (totalETHBalance < 0) { + return 0; + } + + return uint256(totalETHBalance); + + } + + /** + * @notice Retrieves the amount of unverified staked ETH held by this StakingNode. + * @return The amount of unverified staked ETH in wei. + */ + function getUnverifiedStakedETH() public view returns (uint256) { + return unverifiedStakedETH; + } + + /** + * @notice Retrieves the amount of shares currently queued for withdrawal. + * @return The amount of queued shares. + */ + function getQueuedSharesAmount() public view returns (uint256) { + return queuedSharesAmount; + } + + /** + * @notice Retrieves the amount of ETH that has been withdrawn from Eigenlayer and is held by this StakingNode. + Composed of validator principal, validator staking rewards and arbitrary ETH sent to the Eigenpod. + * @return The amount of withdrawn ETH. + */ + function getWithdrawnETH() public view returns (uint256) { + return withdrawnETH; } //-------------------------------------------------------------------------------------- @@ -295,8 +472,10 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea return beacon.implementation(); } - /// @notice Retrieve the version number of the highest/newest initialize - /// function that was executed. + /** + * @notice Retrieve the version number of the highest/newest initialize + * function that was executed. + */ function getInitializedVersion() external view returns (uint64) { return _getInitializedVersion(); } @@ -305,12 +484,14 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea //---------------------------------- MODIFIERS --------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Ensure that the given address is not the zero address. - /// @param _address The address to check. + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ modifier notZeroAddress(address _address) { if (_address == address(0)) { revert ZeroAddress(); } _; } -} \ No newline at end of file +} diff --git a/src/StakingNodesManager.sol b/src/StakingNodesManager.sol index cfc160b12..e0fda433a 100644 --- a/src/StakingNodesManager.sol +++ b/src/StakingNodesManager.sol @@ -9,13 +9,14 @@ import {depositRootGenerator} from "src/external/ethereum/DepositRootGenerator.s import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; import {IDepositContract} from "src/external/ethereum/IDepositContract.sol"; import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import {IRewardsDistributor,IRewardsReceiver, RewardsType} from "src/interfaces/IRewardsDistributor.sol"; import {IEigenPodManager,IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {IStakingNode} from "src/interfaces/IStakingNode.sol"; import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; import {IynETH} from "src/interfaces/IynETH.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; + interface StakingNodesManagerEvents { event StakingNodeCreated(address indexed nodeAddress, address indexed podAddress); @@ -26,8 +27,21 @@ interface StakingNodesManagerEvents { event RegisteredStakingNodeImplementationContract(address upgradeableBeaconAddress, address implementationContract); event UpgradedStakingNodeImplementationContract(address implementationContract, uint256 nodesCount); event NodeInitialized(address nodeAddress, uint64 initializedVersion); + event PrincipalWithdrawalProcessed(uint256 nodeId, uint256 amountToReinvest, uint256 amountToQueue, uint256 rewardsAmount); + event ETHReceived(address sender, uint256 amount); } +/** + * @notice Each node in the StakingNodesManager manages an EigenPod. + * An EigenPod represents a collection of validators and their associated staking activities within the EigenLayer protocol. + * The StakingNode contract, which each node is an instance of, interacts with the EigenPod to perform various operations such as: + * - Creating the EigenPod upon the node's initialization if it does not already exist. + * - Delegating staking operations to the EigenPod, including processing rewards and managing withdrawals. + * - Verifying withdrawal credentials and managing expedited withdrawals before restaking. + * + * This design allows for delegating to multiple operators simultaneously while also being gas efficient. + * Grouping multuple validators per EigenPod allows delegation of all their stake with 1 delegationManager.delegateTo(operator) call. + */ contract StakingNodesManager is IStakingNodesManager, Initializable, @@ -52,6 +66,8 @@ contract StakingNodesManager is error NoValidatorsProvided(); error ValidatorRegistrationPaused(); error InvalidRewardsType(RewardsType rewardsType); + error ValidatorUnused(bytes publicKey); + error ValidatorNotWithdrawn(bytes publicKey, IEigenPod.VALIDATOR_STATUS status); //-------------------------------------------------------------------------------------- //---------------------------------- ROLES ------------------------------------------- @@ -78,6 +94,12 @@ contract StakingNodesManager is /// @notice Role is able to unpause the system bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE"); + /// @notice Role is able to manage withdrawals + bytes32 public constant WITHDRAWAL_MANAGER_ROLE = keccak256("WITHDRAWAL_MANAGER_ROLE"); + + /// @notice Role is able to manage specific withdrawals for staking nodes + bytes32 public constant STAKING_NODES_WITHDRAWER_ROLE = keccak256("STAKING_NODES_WITHDRAWER_ROLE"); + //-------------------------------------------------------------------------------------- //---------------------------------- CONSTANTS --------------------------------------- //-------------------------------------------------------------------------------------- @@ -91,7 +113,8 @@ contract StakingNodesManager is IEigenPodManager public eigenPodManager; IDepositContract public depositContractEth2; IDelegationManager public delegationManager; - IDelayedWithdrawalRouter public delayedWithdrawalRouter; + /// @dev redemptionAssetsVault replaces the slot formerly used by: IDelayedWithdrawalRouter public delayedWithdrawalRouter; + address private ___deprecated_delayedWithdrawalRouter; IStrategyManager public strategyManager; UpgradeableBeacon public upgradeableBeacon; @@ -99,18 +122,6 @@ contract StakingNodesManager is IynETH public ynETH; IRewardsDistributor public rewardsDistributor; - /** - /** - * @notice Each node in the StakingNodesManager manages an EigenPod. - * An EigenPod represents a collection of validators and their associated staking activities within the EigenLayer protocol. - * The StakingNode contract, which each node is an instance of, interacts with the EigenPod to perform various operations such as: - * - Creating the EigenPod upon the node's initialization if it does not already exist. - * - Delegating staking operations to the EigenPod, including processing rewards and managing withdrawals. - * - Verifying withdrawal credentials and managing expedited withdrawals before restaking. - * - * This design allows for delegating to multiple operators simultaneously while also being gas efficient. - * Grouping multuple validators per EigenPod allows delegation of all their stake with 1 delegationManager.delegateTo(operator) call. - */ IStakingNode[] public nodes; uint256 public maxNodeCount; @@ -118,6 +129,7 @@ contract StakingNodesManager is mapping(bytes pubkey => bool) usedValidators; bool public validatorRegistrationPaused; + IRedemptionAssetsVault public redemptionAssetsVault; //-------------------------------------------------------------------------------------- //---------------------------------- INITIALIZATION ---------------------------------- @@ -148,9 +160,14 @@ contract StakingNodesManager is IDepositContract depositContract; IEigenPodManager eigenPodManager; IDelegationManager delegationManager; - IDelayedWithdrawalRouter delayedWithdrawalRouter; IStrategyManager strategyManager; } + + struct Init2 { + IRedemptionAssetsVault redemptionAssetsVault; + address withdrawalManager; + address stakingNodesWithdrawer; + } function initialize(Init calldata init) external @@ -194,7 +211,6 @@ contract StakingNodesManager is notZeroAddress(address(init.depositContract)) notZeroAddress(address(init.eigenPodManager)) notZeroAddress(address(init.delegationManager)) - notZeroAddress(address(init.delayedWithdrawalRouter)) notZeroAddress(address(init.strategyManager)) { // Ethereum depositContractEth2 = init.depositContract; @@ -202,20 +218,40 @@ contract StakingNodesManager is // Eigenlayer eigenPodManager = init.eigenPodManager; delegationManager = init.delegationManager; - delayedWithdrawalRouter = init.delayedWithdrawalRouter; strategyManager = init.strategyManager; } + // TODO: hardcode these values instead of setting them as parameters + function initializeV2(Init2 calldata init) + external + notZeroAddress(address(init.redemptionAssetsVault)) + notZeroAddress(init.withdrawalManager) + notZeroAddress(address(init.stakingNodesWithdrawer)) + reinitializer(2) + onlyRole(DEFAULT_ADMIN_ROLE) { + + // TODO: review role access here for what can execute this + redemptionAssetsVault = init.redemptionAssetsVault; + _grantRole(WITHDRAWAL_MANAGER_ROLE, init.withdrawalManager); + _grantRole(STAKING_NODES_WITHDRAWER_ROLE, init.stakingNodesWithdrawer); + + // Zero out deprecated variable + ___deprecated_delayedWithdrawalRouter = address(0); + } + receive() external payable { - if (msg.sender != address(ynETH)) { - revert DepositorNotYnETH(); - } + emit ETHReceived(msg.sender, msg.value); } //-------------------------------------------------------------------------------------- //---------------------------------- VALIDATOR REGISTRATION -------------------------- //-------------------------------------------------------------------------------------- + /** + * @notice Registers new validators to the system. + * @dev This function can only be called by an account with the `VALIDATOR_MANAGER_ROLE`. + * @param newValidators An array of `ValidatorData` containing the data of the validators to be registered. + */ function registerValidators( ValidatorData[] calldata newValidators ) public onlyRole(VALIDATOR_MANAGER_ROLE) nonReentrant { @@ -293,6 +329,14 @@ contract StakingNodesManager is ); } + /** + * @notice Generates a deposit root hash using the provided validator information and deposit amount. + * @param publicKey The public key of the validator. + * @param signature The signature of the validator. + * @param withdrawalCredentials The withdrawal credentials for the validator. + * @param depositAmount The amount of ETH to be deposited. + * @return The generated deposit root hash as a bytes32 value. + */ function generateDepositRoot( bytes calldata publicKey, bytes calldata signature, @@ -302,26 +346,36 @@ contract StakingNodesManager is return depositRootGenerator.generateDepositRoot(publicKey, signature, withdrawalCredentials, depositAmount); } + /** + * @notice Retrieves the withdrawal credentials for a given node. + * @param nodeId The ID of the node for which to retrieve the withdrawal credentials. + * @return The withdrawal credentials as a byte array. + */ function getWithdrawalCredentials(uint256 nodeId) public view returns (bytes memory) { - address eigenPodAddress = address(IStakingNode(nodes[nodeId]).eigenPod()); return generateWithdrawalCredentials(eigenPodAddress); } - /// @notice Generates withdraw credentials for a validator - /// @param _address associated with the validator for the withdraw credentials - /// @return the generated withdraw key for the node + /** + * @notice Generates withdraw credentials for a validator + * @param _address Address associated with the validator for the withdraw credentials + * @return The generated withdraw key for the node + */ function generateWithdrawalCredentials(address _address) public pure returns (bytes memory) { return abi.encodePacked(bytes1(0x01), bytes11(0x0), _address); } - /// @notice Pauses validator registration. + /** + * @notice Pauses validator registration. + */ function pauseValidatorRegistration() external onlyRole(PAUSER_ROLE) { validatorRegistrationPaused = true; emit ValidatorRegistrationPausedSet(true); } - /// @notice Unpauses validator registration. + /** + * @notice Unpauses validator registration. + */ function unpauseValidatorRegistration() external onlyRole(UNPAUSER_ROLE) { validatorRegistrationPaused = false; emit ValidatorRegistrationPausedSet(false); @@ -330,6 +384,13 @@ contract StakingNodesManager is //---------------------------------- STAKING NODE CREATION --------------------------- //-------------------------------------------------------------------------------------- + /** + * @notice Creates a new staking node using a BeaconProxy. + * @dev This function requires the caller to have the STAKING_NODE_CREATOR_ROLE. + * It checks if the maximum number of staking nodes has been reached and reverts if so. + * A new BeaconProxy is created and initialized, and a new EigenPod is created for the node. + * @return node The newly created IStakingNode instance. + */ function createStakingNode() public notZeroAddress((address(upgradeableBeacon))) @@ -345,7 +406,6 @@ contract StakingNodesManager is BeaconProxy proxy = new BeaconProxy(address(upgradeableBeacon), ""); IStakingNode node = IStakingNode(payable(proxy)); - initializeStakingNode(node, nodeCount); IEigenPod eigenPod = node.createEigenPod(); @@ -357,22 +417,40 @@ contract StakingNodesManager is return node; } + /** + * @notice Initializes a staking node with the necessary version-specific initializations. + * @dev This function handles the versioned initialization of a staking node. It checks the current + * initialized version of the node and performs the necessary initialization steps. If the node + * is at version 0, it initializes it to version 1. If the node is at version 1, it initializes + * it to version 2. This function should be extended with additional conditions for future versions. + * @param node The staking node to initialize. + * @param nodeCount The index of the node in the nodes array, used for initialization parameters. + */ function initializeStakingNode(IStakingNode node, uint256 nodeCount) virtual internal { - uint64 initializedVersion = node.getInitializedVersion(); if (initializedVersion == 0) { node.initialize( IStakingNode.Init(IStakingNodesManager(address(this)), nodeCount) ); - // update to the newly upgraded version. + // Update to the newly upgraded version. initializedVersion = node.getInitializedVersion(); emit NodeInitialized(address(node), initializedVersion); } - // NOTE: for future versions add additional if clauses that initialize the node - // for the next version while keeping the previous initializers + + if (initializedVersion == 1) { + node.initializeV2(0); + } + + // NOTE: For future versions, add additional if clauses that initialize the node + // for the next version while keeping the previous initializers. } + /** + * @notice Registers a new implementation contract for staking nodes by creating a new upgradeable beacon. + * @dev This function can only be called by an account with the STAKING_ADMIN_ROLE. It will fail if a beacon implementation already exists. + * @param _implementationContract The address of the new implementation contract for staking nodes. + */ function registerStakingNodeImplementationContract(address _implementationContract) public onlyRole(STAKING_ADMIN_ROLE) @@ -387,6 +465,11 @@ contract StakingNodesManager is emit RegisteredStakingNodeImplementationContract(address(upgradeableBeacon), _implementationContract); } + /** + * @notice Upgrades the staking node implementation to a new contract. + * @dev This function can only be called by an account with the STAKING_ADMIN_ROLE. It will fail if no beacon implementation exists. + * @param _implementationContract The address of the new implementation contract for staking nodes. + */ function upgradeStakingNodeImplementation(address _implementationContract) public onlyRole(STAKING_ADMIN_ROLE) @@ -398,16 +481,18 @@ contract StakingNodesManager is uint256 nodeCount = nodes.length; - // reinitialize all nodes + // Reinitialize all nodes for (uint256 i = 0; i < nodeCount; i++) { initializeStakingNode(nodes[i], nodeCount); } emit UpgradedStakingNodeImplementationContract(_implementationContract, nodeCount); } - - /// @notice Sets the maximum number of staking nodes allowed - /// @param _maxNodeCount The maximum number of staking nodes + + /** + * @notice Sets the maximum number of staking nodes allowed + * @param _maxNodeCount The maximum number of staking nodes + */ function setMaxNodeCount(uint256 _maxNodeCount) public onlyRole(STAKING_ADMIN_ROLE) { maxNodeCount = _maxNodeCount; emit MaxNodeCountUpdated(_maxNodeCount); @@ -417,12 +502,20 @@ contract StakingNodesManager is //---------------------------------- WITHDRAWALS ------------------------------------- //-------------------------------------------------------------------------------------- + /** + * @notice Processes and forwards rewards to the appropriate rewards receiver based on the type of rewards. + * @dev This function can only be called by the staking node itself. + * @param nodeId The ID of the staking node sending the rewards. + * @param rewardsType The type of rewards being processed (ConsensusLayer or ExecutionLayer). + */ function processRewards(uint256 nodeId, RewardsType rewardsType) external payable { if (address(nodes[nodeId]) != msg.sender) { revert NotStakingNode(msg.sender, nodeId); } + _processRewards(nodeId, rewardsType, msg.value); + } - uint256 rewards = msg.value; + function _processRewards(uint256 nodeId, RewardsType rewardsType, uint256 rewards) internal { IRewardsReceiver receiver; if (rewardsType == RewardsType.ConsensusLayer) { @@ -438,39 +531,179 @@ contract StakingNodesManager is revert TransferFailed(); } - emit WithdrawnETHRewardsProcessed(nodeId, rewardsType, msg.value); + emit WithdrawnETHRewardsProcessed(nodeId, rewardsType, rewards); + } + + /** + * @notice Processes an array of principal withdrawals. + * @param actions Array of WithdrawalAction containing details for each withdrawal. + */ + function processPrincipalWithdrawals( + WithdrawalAction[] memory actions + ) public onlyRole(WITHDRAWAL_MANAGER_ROLE) { + for (uint256 i = 0; i < actions.length; i++) { + _processPrincipalWithdrawalForNode(actions[i]); + } + } + + /** + * @notice Processes principal withdrawals for a single node, specifying how much goes back into ynETH and how much goes to the withdrawal queue. + * @param action The WithdrawalAction containing details for the withdrawal. + */ + function _processPrincipalWithdrawalForNode(WithdrawalAction memory action) internal { + uint256 nodeId = action.nodeId; + uint256 amountToReinvest = action.amountToReinvest; + uint256 amountToQueue = action.amountToQueue; + + // The rewardsAmount is trusted off-chain input provided in the WithdrawalAction struct. + // It represents the portion of the withdrawn amount that is considered as rewards. + // This value is determined off-chain by analyzing the difference between + // the initial stake and the total withdrawn amount. + // + // This design trade-off is a result of how Eigenlayer M3 pepe no long providees + // clear separation between principal and rewards amount and they both exit through the + // Queued Withdrawals mechanism. + // + // SECURITY NOTE: + // The accuracy and integrity of this value relies on the off-chain process + // that calculates it. There's an implicit trust that the WITHDRAWAL_MANAGER_ROLE + // will provide correct and verified data and that principal is not counted as Rewards + // and applied a fee. + uint256 rewardsAmount = action.rewardsAmount; + + // Calculate the total amount to be processed by summing reinvestment, rewards and queuing amounts + uint256 totalAmount = amountToReinvest + amountToQueue + rewardsAmount; + + // Retrieve the staking node object using the nodeId + IStakingNode node = nodes[nodeId]; + + // Deallocate the specified total amount of ETH from the staking node + node.deallocateStakedETH(totalAmount); + + + // If there is an amount specified to reinvest, process it through ynETH + if (amountToReinvest > 0) { + ynETH.processWithdrawnETH{value: amountToReinvest}(); + } + + // If there is an amount specified to queue, send it to the withdrawal assets vault + if (amountToQueue > 0) { + (bool success, ) = address(redemptionAssetsVault).call{value: amountToQueue}(""); + if (!success) { + revert TransferFailed(); + } + } + + // If there is an amount of rewards specified, handle that + if (rewardsAmount > 0) { + + // IMPORTANT: Impact on totalAssets() + // After charging the rewards fee, the totalAssets() of the system may decrease. + // Steps: + // 1. The full rewardsAmount is removed from the staking node's balance (which is part of totalAssets). + // 2. Only the remainingRewards (after fees) are reinvested back to the system. + // 3. The fees are sent to a separate fee receiver and are no longer part of the system's totalAssets. + + (bool sent, ) = address(rewardsDistributor.consensusLayerReceiver()).call{value: rewardsAmount}(""); + if (!sent) { + revert TransferFailed(); + } + // process rewards immediately to avoid large totalAssets() fluctuations + rewardsDistributor.processRewards(); + } + + // Emit an event to log the processed principal withdrawal details + emit PrincipalWithdrawalProcessed(nodeId, amountToReinvest, amountToQueue, rewardsAmount); } //-------------------------------------------------------------------------------------- //---------------------------------- VIEWS ------------------------------------------- //-------------------------------------------------------------------------------------- + /** + * @notice Retrieves all registered validators. + * @return An array of Validator structs representing all registered validators. + */ function getAllValidators() public view returns (Validator[] memory) { return validators; } + /** + * @notice Retrieves all staking nodes. + * @return An array of IStakingNode contracts representing all staking nodes. + */ function getAllNodes() public view returns (IStakingNode[] memory) { return nodes; } + /** + * @notice Gets the total number of staking nodes. + * @return The number of staking nodes. + */ function nodesLength() public view returns (uint256) { return nodes.length; } + /** + * @notice Checks if the given address has the STAKING_NODES_OPERATOR_ROLE. + * @param _address The address to check. + * @return True if the address has the STAKING_NODES_OPERATOR_ROLE, false otherwise. + */ function isStakingNodesOperator(address _address) public view returns (bool) { return hasRole(STAKING_NODES_OPERATOR_ROLE, _address); } + /** + * @notice Checks if the given address has the STAKING_NODES_DELEGATOR_ROLE. + * @param _address The address to check. + * @return True if the address has the STAKING_NODES_DELEGATOR_ROLE, false otherwise. + */ function isStakingNodesDelegator(address _address) public view returns (bool) { return hasRole(STAKING_NODES_DELEGATOR_ROLE, _address); } + /** + * @notice Checks if the given address has the STAKING_NODES_WITHDRAWER_ROLE. + * @param _address The address to check. + * @return True if the address has the STAKING_NODES_WITHDRAWER_ROLE, false otherwise. + */ + function isStakingNodesWithdrawer(address _address) public view returns (bool) { + return hasRole(STAKING_NODES_WITHDRAWER_ROLE, _address); + } + + /** + * @notice Calculates the total amount of ETH deposited across all staking nodes and includes available redemption assets. + * @dev This function sums the ETH balances of all staking nodes and optionally includes the ETH available in the redemption assets vault. + * Including the redemption assets can expose the system to a donation attack if not properly bootstrapped. + * @return totalETHDeposited The total amount of ETH deposited in the system. + */ + function totalDeposited() external view returns (uint256) { + + uint256 _nodesLength = nodes.length; + + uint256 totalETHDeposited = 0; + + for (uint256 i = 0; i < _nodesLength; i++) { + totalETHDeposited += nodes[i].getETHBalance(); + } + + // NOTE: Counting the availableRedemptionAssets towards totalDeposited + // opens up ynETH to donation attack for a non boostrapped system. + if (address(redemptionAssetsVault) != address(0)) { + totalETHDeposited += redemptionAssetsVault.availableRedemptionAssets(); + } + + return totalETHDeposited; + } + //-------------------------------------------------------------------------------------- //---------------------------------- MODIFIERS --------------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Ensure that the given address is not the zero address. - /// @param _address The address to check. + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ modifier notZeroAddress(address _address) { if (_address == address(0)) { revert ZeroAddress(); diff --git a/src/WithdrawalQueueManager.sol b/src/WithdrawalQueueManager.sol new file mode 100644 index 000000000..0a72c28f9 --- /dev/null +++ b/src/WithdrawalQueueManager.sol @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol"; +import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IWithdrawalQueueManager} from "src/interfaces/IWithdrawalQueueManager.sol"; +import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import {IRedeemableAsset} from "src/interfaces/IRedeemableAsset.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; +import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ERC721EnumerableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; +import {IERC721} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import {SafeCast} from "lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; + +interface IWithdrawalQueueManagerEvents { + event WithdrawalRequested( + uint256 indexed tokenId, + address indexed requester, + IWithdrawalQueueManager.WithdrawalRequest request + ); + event WithdrawalClaimed( + uint256 indexed tokenId, + address claimer, + address receiver, + IWithdrawalQueueManager.WithdrawalRequest request, + uint256 finalizationId, + uint256 unitOfAccountAmount, + uint256 claimRedemptionRate + ); + event WithdrawalFeeUpdated(uint256 newFeePercentage); + event FeeReceiverUpdated(address indexed oldFeeReceiver, address indexed newFeeReceiver); + event SecondsToFinalizationUpdated(uint256 previousValue, uint256 newValue); + event RequestsFinalized(uint256 indexed finalizationIndex, uint256 newFinalizedIndex, uint256 previousFinalizedIndex, uint256 redemptionRate); + event SurplusRedemptionAssetsWithdrawn(uint256 amount, uint256 surplus); +} + +/** + * @title Withdrawal Queue Manager for Redeemable Assets + * @dev Manages the queue of withdrawal requests for redeemable assets, handling fees, finalization times, and claims. + * This contract extends ERC721 to represent each withdrawal request as a unique token. + * + */ + +contract WithdrawalQueueManager is IWithdrawalQueueManager, ERC721EnumerableUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, IWithdrawalQueueManagerEvents { + using SafeERC20 for IRedeemableAsset; + + //-------------------------------------------------------------------------------------- + //---------------------------------- ERRORS ------------------------------------------- + //-------------------------------------------------------------------------------------- + + error NotFinalized(uint256 tokenId); + error ZeroAddress(); + error WithdrawalAlreadyProcessed(uint256 tokenId); + error InsufficientBalance(uint256 currentBalance, uint256 requestedBalance); + error CallerNotOwnerNorApproved(uint256 tokenId, address caller); + error AmountExceedsSurplus(uint256 requestedAmount, uint256 availableSurplus); + error AmountMustBeGreaterThanZero(); + error FeePercentageExceedsLimit(); + error ArrayLengthMismatch(uint256 length1, uint256 length2); + error SecondsToFinalizationExceedsLimit(uint256 value); + error WithdrawalRequestDoesNotExist(uint256 tokenId); + error IndexExceedsTokenCount(uint256 index, uint256 tokenCount); + error IndexNotAdvanced(uint256 newIndex, uint256 currentIndex); + error InvalidFinalizationId(uint256 finalizationId); + error TokenIdNotInFinalizationRange(uint256 tokenId, uint256 finalizationId, uint256 startIndex, uint256 endIndex); + + //-------------------------------------------------------------------------------------- + //---------------------------------- ROLES ------------------------------------------- + //-------------------------------------------------------------------------------------- + + /// @dev Role identifier for administrators who can manage the withdrawal queue settings. + bytes32 public constant WITHDRAWAL_QUEUE_ADMIN_ROLE = keccak256("WITHDRAWAL_QUEUE_ADMIN_ROLE"); + + /// @dev Role identifier for accounts authorized to withdraw surplus redemption assets. + bytes32 public constant REDEMPTION_ASSET_WITHDRAWER_ROLE = keccak256("REDEMPTION_ASSET_WITHDRAWER_ROLE"); + + /// @dev Role identifier for accounts authorized to finalize withdrawal requests. + bytes32 public constant REQUEST_FINALIZER_ROLE = keccak256("REQUEST_FINALIZER_ROLE"); + + //-------------------------------------------------------------------------------------- + //---------------------------------- CONSTANTS --------------------------------------- + //-------------------------------------------------------------------------------------- + + uint256 constant public FEE_PRECISION = 1000000; + uint256 constant public MAX_SECONDS_TO_FINALIZATION = 3600 * 24 * 28; // 4 weeks + + //-------------------------------------------------------------------------------------- + //---------------------------------- VARIABLES --------------------------------------- + //-------------------------------------------------------------------------------------- + + /// @notice The asset that can be redeemed through withdrawal requests. + IRedeemableAsset public redeemableAsset; + + /// @notice The vault where redemption assets are stored. + IRedemptionAssetsVault public redemptionAssetsVault; + + /// @notice Counter for tracking the next token ID to be assigned. + uint256 public _tokenIdCounter; + + /// @notice Mapping of token IDs to their corresponding withdrawal requests. + mapping(uint256 => WithdrawalRequest) public withdrawalRequests; + + /// @notice The required duration in seconds between a withdrawal request and when it can be finalized. + uint256 public secondsToFinalization; + + /// @notice The fee percentage charged on withdrawals. + uint256 public withdrawalFee; + + /// @notice The address where withdrawal fees are sent. + address public feeReceiver; + + /// @notice The address authorized to finalize withdrawal requests. + address public requestFinalizer; + + /// @notice pending requested redemption amount in redemption unit of account + uint256 public pendingRequestedRedemptionAmount; + + uint256 public lastFinalizedIndex; + + /// @notice Array to store finalization data + Finalization[] public finalizations; + + //-------------------------------------------------------------------------------------- + //---------------------------------- INITIALIZATION ---------------------------------- + //-------------------------------------------------------------------------------------- + + constructor() { + _disableInitializers(); + } + + struct Init { + string name; + string symbol; + IRedeemableAsset redeemableAsset; + IRedemptionAssetsVault redemptionAssetsVault; + address admin; + address withdrawalQueueAdmin; + address redemptionAssetWithdrawer; + uint256 withdrawalFee; + address feeReceiver; + address requestFinalizer; + + } + + function initialize(Init memory init) + public + notZeroAddress(address(init.admin)) + notZeroAddress(address(init.redeemableAsset)) + notZeroAddress(address(init.redemptionAssetsVault)) + notZeroAddress(address(init.withdrawalQueueAdmin)) + notZeroAddress(address(init.feeReceiver)) + notZeroAddress(address(init.requestFinalizer)) + + initializer { + __ERC721_init(init.name, init.symbol); + redeemableAsset = init.redeemableAsset; + redemptionAssetsVault = init.redemptionAssetsVault; + + _grantRole(DEFAULT_ADMIN_ROLE, init.admin); + _grantRole(WITHDRAWAL_QUEUE_ADMIN_ROLE, init.withdrawalQueueAdmin); + _grantRole(REDEMPTION_ASSET_WITHDRAWER_ROLE, init.redemptionAssetWithdrawer); + _grantRole(REQUEST_FINALIZER_ROLE, init.requestFinalizer); + + withdrawalFee = init.withdrawalFee; + feeReceiver = init.feeReceiver; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- WITHDRAWAL REQUESTS ----------------------------- + //-------------------------------------------------------------------------------------- + + + /** + * @notice Requests a withdrawal of a specified amount of redeemable assets without additional data. + * @dev This is a convenience function that calls the main requestWithdrawal function with empty data. + * @param amount The amount of redeemable assets to withdraw. + * @return tokenId The token ID associated with the withdrawal request. + */ + function requestWithdrawal(uint256 amount) external returns (uint256 tokenId) { + return requestWithdrawal(amount, ""); + } + + /** + * @notice Requests a withdrawal of a specified amount of redeemable assets. + * @dev Transfers the specified amount of redeemable assets from the sender to this contract, creates a withdrawal request, + * and mints a token representing this request. Emits a WithdrawalRequested event upon success. + * @param amount The amount of redeemable assets to withdraw. + * @param data Extra data payload associated with the request + * @return tokenId The token ID associated with the withdrawal request. + */ + function requestWithdrawal(uint256 amount, bytes memory data) public nonReentrant returns (uint256 tokenId) { + if (amount == 0) { + revert AmountMustBeGreaterThanZero(); + } + + redeemableAsset.safeTransferFrom(msg.sender, address(this), amount); + + uint256 currentRate = redemptionAssetsVault.redemptionRate(); + tokenId = _tokenIdCounter++; + WithdrawalRequest memory request = WithdrawalRequest({ + amount: amount, + feeAtRequestTime: withdrawalFee, + redemptionRateAtRequestTime: currentRate, + creationTimestamp: block.timestamp, + processed: false, + data: data + }); + withdrawalRequests[tokenId] = request; + + pendingRequestedRedemptionAmount += calculateRedemptionAmount(amount, currentRate); + + _mint(msg.sender, tokenId); + + emit WithdrawalRequested(tokenId, msg.sender, request); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- CLAIMS ------------------------------------------ + //-------------------------------------------------------------------------------------- + + + /** + * @notice Claims a withdrawal for a specific token ID and transfers the assets to the specified receiver. + * @dev This function burns the token representing the withdrawal request and transfers the net amount + after fees to the receiver. + * It also transfers the fee to the fee receiver. + * It automatically finds the finalization ID for the given token ID. + * @param tokenId The ID of the token representing the withdrawal request. + * @param receiver The address to which the withdrawn assets will be sent. + */ + function claimWithdrawal(uint256 tokenId, address receiver) public nonReentrant { + WithdrawalClaim memory claim = WithdrawalClaim({ + tokenId: tokenId, + receiver: receiver, + finalizationId: findFinalizationForTokenId(tokenId) + }); + _claimWithdrawal(claim); + } + + /** + * @notice Claims a withdrawal by transferring the requested assets to the specified receiver, less any applicable fees. + * @dev This function burns the token representing the withdrawal request and transfers the net amount after fees to the receiver. + * It also transfers the fee to the fee receiver. + * @param claim The claim struct contains: + * the tokenId The ID of the token representing the withdrawal request, + * the receiver as the address to which the net amount of the withdrawal will be sent. + * the finalizationId for the particular finalization which denotes the rate at finalization time. + */ + function claimWithdrawal(WithdrawalClaim memory claim) public nonReentrant { + _claimWithdrawal(claim); + } + + function _claimWithdrawal(WithdrawalClaim memory claim) internal { + + uint256 tokenId = claim.tokenId; + uint256 finalizationId = claim.finalizationId; + address receiver = claim.receiver; + + if (_ownerOf(claim.tokenId) != msg.sender && _getApproved(claim.tokenId) != msg.sender) { + revert CallerNotOwnerNorApproved(claim.tokenId, msg.sender); + } + + // Check if the finalization ID is valid + if (finalizationId >= finalizations.length) { + revert InvalidFinalizationId(finalizationId); + } + + Finalization memory finalization = finalizations[finalizationId]; + + // Check if the token ID is within the finalized range + if (tokenId < finalization.startIndex || tokenId >= finalization.endIndex) { + revert TokenIdNotInFinalizationRange(tokenId, finalizationId, finalization.startIndex, finalization.endIndex); + } + + // Update the redemption rate to use the one from the finalization + uint256 redemptionRateAtFinalization = finalization.redemptionRate; + + WithdrawalRequest memory request = withdrawalRequests[tokenId]; + if (!withdrawalRequestExists(request)) { + revert WithdrawalRequestDoesNotExist(tokenId); + } + + if (request.processed) { + revert WithdrawalAlreadyProcessed(tokenId); + } + + if (!withdrawalRequestIsFinalized(tokenId)) { + revert NotFinalized(tokenId); + } + + withdrawalRequests[tokenId].processed = true; + uint256 redemptionRate = ( + request.redemptionRateAtRequestTime < redemptionRateAtFinalization + ? request.redemptionRateAtRequestTime + : redemptionRateAtFinalization + ); + + uint256 unitOfAccountAmount = calculateRedemptionAmount(request.amount, redemptionRate); + + pendingRequestedRedemptionAmount -= unitOfAccountAmount; + + _burn(tokenId); + redeemableAsset.burn(request.amount); + + uint256 feeAmount = calculateFee(unitOfAccountAmount, request.feeAtRequestTime); + + uint256 currentBalance = redemptionAssetsVault.availableRedemptionAssets(); + if (currentBalance < unitOfAccountAmount) { + revert InsufficientBalance(currentBalance, unitOfAccountAmount); + } + + // Transfer net amount (unitOfAccountAmount - feeAmount) to the receiver + redemptionAssetsVault.transferRedemptionAssets(receiver, unitOfAccountAmount - feeAmount, request.data); + + if (feeAmount > 0) { + redemptionAssetsVault.transferRedemptionAssets(feeReceiver, feeAmount, request.data); + } + + emit WithdrawalClaimed(tokenId, msg.sender, receiver, request, finalizationId, unitOfAccountAmount, redemptionRate); + } + + /** + * @notice Allows a batch of withdrawals to be claimed by their respective token IDs. + * @param tokenIds An array of token IDs corresponding to the withdrawal requests to be claimed. + * @param receivers An array of addresses to receive the claimed withdrawals. + * @dev The length of tokenIds and receivers must be the same. + */ + function claimWithdrawals(uint256[] calldata tokenIds, address[] calldata receivers) external { + if (tokenIds.length != receivers.length) { + revert ArrayLengthMismatch(tokenIds.length, receivers.length); + } + + for (uint256 i = 0; i < tokenIds.length; i++) { + claimWithdrawal(tokenIds[i], receivers[i]); + } + } + + /** + * @notice Allows a batch of withdrawals to be claimed by their respective token IDs. + * @param claims An array of claims corresponding to the withdrawal requests to be claimed. + */ + function claimWithdrawals(WithdrawalClaim[] calldata claims) external { + for (uint256 i = 0; i < claims.length; i++) { + WithdrawalClaim memory claim = claims[i]; + claimWithdrawal(claim); + } + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- ADMIN ------------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Sets the withdrawal fee percentage. + * @param feePercentage The fee percentage in basis points. + */ + function setWithdrawalFee(uint256 feePercentage) external onlyRole(WITHDRAWAL_QUEUE_ADMIN_ROLE) { + if (feePercentage > FEE_PRECISION) { + revert FeePercentageExceedsLimit(); + } + withdrawalFee = feePercentage; + emit WithdrawalFeeUpdated(feePercentage); + } + + /** + * @notice Sets the address where withdrawal fees are sent. + * @param _feeReceiver The address that will receive the withdrawal fees. + */ + function setFeeReceiver( + address _feeReceiver + ) external notZeroAddress(_feeReceiver) onlyRole(WITHDRAWAL_QUEUE_ADMIN_ROLE) { + + emit FeeReceiverUpdated(feeReceiver, _feeReceiver); + feeReceiver = _feeReceiver; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- COMPUTATIONS ------------------------------------ + //-------------------------------------------------------------------------------------- + + /** + * @notice Calculates the redemption amount based on the provided amount and the redemption rate at the time of request. + * @param amount The amount of the redeemable asset. + * @param redemptionRate The redemption rate expressed in the same unit of decimals as the redeemable asset. + * @return The calculated redemption amount, adjusted for the decimal places of the redeemable asset. + */ + function calculateRedemptionAmount( + uint256 amount, + uint256 redemptionRate + ) public view returns (uint256) { + return amount * redemptionRate / (10 ** redeemableAsset.decimals()); + } + + /** + * @notice Calculates the withdrawal fee based on the amount and the current fee percentage. + * @param amount The amount from which the fee should be calculated. + * @param requestWithdrawalFee The current fee percentage in basis points. + * @return fee The calculated fee. + */ + function calculateFee(uint256 amount, uint256 requestWithdrawalFee) public pure returns (uint256) { + return (amount * requestWithdrawalFee) / FEE_PRECISION; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- REDEMPTION ASSETS ------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Calculates the surplus of redemption assets after accounting for all pending withdrawals. + * @return surplus The amount of surplus redemption assets in the unit of account. + */ + function surplusRedemptionAssets() public view returns (uint256) { + uint256 availableAmount = redemptionAssetsVault.availableRedemptionAssets(); + if (availableAmount > pendingRequestedRedemptionAmount) { + return availableAmount - pendingRequestedRedemptionAmount; + } + + return 0; + } + + /** + * @notice Calculates the deficit of redemption assets after accounting for all pending withdrawals. + * @return deficit The amount of deficit redemption assets in the unit of account. + */ + function deficitRedemptionAssets() public view returns (uint256) { + uint256 availableAmount = redemptionAssetsVault.availableRedemptionAssets(); + if (pendingRequestedRedemptionAmount > availableAmount) { + return pendingRequestedRedemptionAmount - availableAmount; + } + + return 0; + } + + /** + * @notice Withdraws surplus redemption assets to a specified address. + */ + function withdrawSurplusRedemptionAssets(uint256 amount) external onlyRole(REDEMPTION_ASSET_WITHDRAWER_ROLE) { + uint256 surplus = surplusRedemptionAssets(); + if (amount > surplus) { + revert AmountExceedsSurplus(amount, surplus); + } + redemptionAssetsVault.withdrawRedemptionAssets(amount); + + emit SurplusRedemptionAssetsWithdrawn(amount, surplus); + } + //-------------------------------------------------------------------------------------- + //---------------------------------- FINALITY ---------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Checks if a withdrawal request with a given index is finalized. + * @param index The index of the withdrawal request. + * @return True if the request is finalized, false otherwise. + */ + function withdrawalRequestIsFinalized(uint256 index) public view returns (bool) { + return index < lastFinalizedIndex; + } + + /** + * @notice Marks all requests whose index is less than lastFinalizedIndex as finalized. + The current redemptionRate rated is recorded as the finalization redemption rate. + * @param _lastFinalizedIndex The index up to which withdrawal requests are considered finalized. + * @dev A lastFinalizedIndex = 0 means no requests are processed. lastFinalizedIndex = 2 means + requests 0 and 1 are processed. + */ + function finalizeRequestsUpToIndex(uint256 _lastFinalizedIndex) + external + onlyRole(REQUEST_FINALIZER_ROLE) + returns (uint256 finalizationIndex) + { + + uint256 currentRate = redemptionAssetsVault.redemptionRate(); + + // Create a new Finalization struct + Finalization memory newFinalization = Finalization({ + startIndex: SafeCast.toUint64(lastFinalizedIndex), + endIndex: SafeCast.toUint64(_lastFinalizedIndex), + redemptionRate: SafeCast.toUint96(currentRate) + }); + + finalizationIndex = finalizations.length; + + // Add the new Finalization to the array + finalizations.push(newFinalization); + + if (_lastFinalizedIndex > _tokenIdCounter) { + revert IndexExceedsTokenCount(_lastFinalizedIndex, _tokenIdCounter); + } + if (_lastFinalizedIndex <= lastFinalizedIndex) { + revert IndexNotAdvanced(_lastFinalizedIndex, lastFinalizedIndex); + } + emit RequestsFinalized(finalizationIndex, _lastFinalizedIndex, lastFinalizedIndex, currentRate); + + lastFinalizedIndex = _lastFinalizedIndex; + } + + /** + * @notice Finds the finalization ID for a given token ID using binary search. + * @param tokenId The token ID to find the finalization for. + * @return finalizationId The ID of the finalization that includes the given token ID. + * @dev The complexity of this algorithm is Math.log2(n) and it is UNBOUNDED + */ + function findFinalizationForTokenId(uint256 tokenId) public view returns (uint256 finalizationId) { + + uint256 finalizationsLength = finalizations.length; + if (finalizationsLength == 0) { + revert NotFinalized(tokenId); + } + + uint256 left = 0; + uint256 right = finalizationsLength - 1; + + while (left <= right) { + uint256 mid = (left + right) / 2; + Finalization memory finalization = finalizations[mid]; + + if (tokenId >= finalization.startIndex && tokenId < finalization.endIndex) { + return mid; + } else if (tokenId < finalization.startIndex) { + right = mid - 1; + } else { + left = mid + 1; + } + } + + revert NotFinalized(tokenId); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- VIEWS ------------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Returns the details of a withdrawal request. + * @param tokenId The token ID of the withdrawal request. + * @return request The withdrawal request details. + */ + function withdrawalRequest(uint256 tokenId) public view returns (WithdrawalRequest memory request) { + request = withdrawalRequests[tokenId]; + if (!withdrawalRequestExists(request)) { + revert WithdrawalRequestDoesNotExist(tokenId); + } + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControlUpgradeable, ERC721EnumerableUpgradeable) returns (bool) { + return interfaceId == type(IERC721).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @notice Checks if a withdrawal request exists. + * @param request The withdrawal request to check. + * @return True if the request exists, false otherwise. + * @dev Reverts with WithdrawalRequestDoesNotExist if the request does not exist. + */ + function withdrawalRequestExists(WithdrawalRequest memory request) internal view returns (bool) { + return request.creationTimestamp > 0; + } + + function withdrawalRequestsForOwner(address owner) public view returns ( + uint256[] memory withdrawalIndexes, + WithdrawalRequest[] memory requests + ) { + + uint256 tokenCount = balanceOf(owner); + if (tokenCount == 0) { + return (new uint256[](0), new WithdrawalRequest[](0)); + } else { + + withdrawalIndexes = new uint256[](tokenCount); + requests = new WithdrawalRequest[](tokenCount); + for (uint256 i = 0; i < tokenCount; i++) { + uint256 tokenId = tokenOfOwnerByIndex(owner, i); + withdrawalIndexes[i] = tokenId; + requests[i] = withdrawalRequests[tokenId]; + } + return (withdrawalIndexes, requests); + } + } + + /** + * @notice Returns the details of a finalization. + * @param finalizationId The ID of the finalization. + * @return finalization The finalization details. + */ + function getFinalization(uint256 finalizationId) public view returns (Finalization memory finalization) { + if (finalizationId >= finalizations.length) { + revert InvalidFinalizationId(finalizationId); + } + finalization = finalizations[finalizationId]; + } + + /** + * @notice Returns the total number of finalizations. + * @return count The number of finalizations. + */ + function finalizationsCount() public view returns (uint256 count) { + return finalizations.length; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- MODIFIERS --------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ + modifier notZeroAddress(address _address) { + if (_address == address(0)) { + revert ZeroAddress(); + } + _; + } +} + diff --git a/src/WithdrawalsProcessor.sol b/src/WithdrawalsProcessor.sol new file mode 100644 index 000000000..538bc6e88 --- /dev/null +++ b/src/WithdrawalsProcessor.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IStakingNodesManager} from "./interfaces/IStakingNodesManager.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IStakingNodesManager, IStakingNodesManager as WithdrawalAction} from "./interfaces/IStakingNodesManager.sol"; +import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; +import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol"; + +interface IWithdrawalsProcessorEvents { + event WithdrawalsCompletedAndProcessed( + IStakingNodesManager.WithdrawalAction withdrawalAction, + uint256 withdrawalsCount + ); +} + +contract WithdrawalsProcessor is Initializable, AccessControlUpgradeable, IWithdrawalsProcessorEvents { + + //-------------------------------------------------------------------------------------- + //---------------------------------- ERRORS ------------------------------------------ + //-------------------------------------------------------------------------------------- + + error ZeroAddress(); + + //-------------------------------------------------------------------------------------- + //---------------------------------- ROLES ------------------------------------------- + //-------------------------------------------------------------------------------------- + + bytes32 public constant WITHDRAWAL_MANAGER_ROLE = keccak256("WITHDRAWAL_MANAGER_ROLE"); + + //-------------------------------------------------------------------------------------- + //---------------------------------- VARIABLES --------------------------------------- + //-------------------------------------------------------------------------------------- + + IStakingNodesManager public stakingNodesManager; + + //-------------------------------------------------------------------------------------- + //---------------------------------- INITIALIZATION ---------------------------------- + //-------------------------------------------------------------------------------------- + + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + IStakingNodesManager _stakingNodesManager, + address _admin, + address _withdrawalManager + ) public initializer + notZeroAddress(address(_stakingNodesManager)) + notZeroAddress(_withdrawalManager) + { + __AccessControl_init(); + + stakingNodesManager = _stakingNodesManager; + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + _grantRole(WITHDRAWAL_MANAGER_ROLE, _withdrawalManager); + } + + /** + * @notice Bundles the completion of queued withdrawals and processing of principal withdrawals for a single node + * @param withdrawalAction The withdrawal action containing node ID and withdrawal amounts + * @param withdrawals Array of withdrawals to complete + * @param middlewareTimesIndexes Array of middleware times indexes for the withdrawals + */ + function completeAndProcessWithdrawalsForNode( + IStakingNodesManager.WithdrawalAction memory withdrawalAction, + IDelegationManager.Withdrawal[] memory withdrawals, + uint256[] memory middlewareTimesIndexes + ) external onlyRole(WITHDRAWAL_MANAGER_ROLE) { + // Complete queued withdrawals + stakingNodesManager.nodes(withdrawalAction.nodeId).completeQueuedWithdrawals(withdrawals, middlewareTimesIndexes); + + // Process principal withdrawal + IStakingNodesManager.WithdrawalAction[] memory actions = new IStakingNodesManager.WithdrawalAction[](1); + actions[0] = withdrawalAction; + stakingNodesManager.processPrincipalWithdrawals(actions); + + // Emit an event for the completed and processed withdrawals + emit WithdrawalsCompletedAndProcessed( + withdrawalAction, + withdrawals.length + ); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- MODIFIERS --------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ + modifier notZeroAddress(address _address) { + if (_address == address(0)) { + revert ZeroAddress(); + } + _; + } +} diff --git a/src/external/ethereum/DepositRootGenerator.sol b/src/external/ethereum/DepositRootGenerator.sol index 0b3c7f497..9cc1dfc2b 100644 --- a/src/external/ethereum/DepositRootGenerator.sol +++ b/src/external/ethereum/DepositRootGenerator.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; library depositRootGenerator { - uint constant GWEI = 1e9; + uint256 constant GWEI = 1e9; function generateDepositRoot( bytes calldata pubkey, @@ -12,7 +12,7 @@ library depositRootGenerator { uint256 _amountIn ) internal pure returns (bytes32) { - uint deposit_amount = _amountIn / GWEI; + uint256 deposit_amount = _amountIn / GWEI; bytes memory amount = to_little_endian_64(uint64(deposit_amount)); bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); diff --git a/src/external/tokens/WETH.sol b/src/external/tokens/WETH.sol deleted file mode 100644 index 6dd052a9a..000000000 --- a/src/external/tokens/WETH.sol +++ /dev/null @@ -1,69 +0,0 @@ -/// SPDX-License-Identifier: MIT -pragma solidity >0.8.0; - -contract WETH { - string public name = "Wrapped Ether"; - string public symbol = "WETH"; - uint8 public decimals = 18; - - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); - - mapping(address => uint) public balanceOf; - mapping(address => mapping(address => uint)) public allowance; - - fallback() external payable { - deposit(); - } - - receive() external payable { - deposit(); - } - - function deposit() public payable { - balanceOf[msg.sender] += msg.value; - emit Deposit(msg.sender, msg.value); - } - - function withdraw(uint wad) public { - require(balanceOf[msg.sender] >= wad, "Insufficient balance"); - balanceOf[msg.sender] -= wad; - payable(msg.sender).transfer(wad); - emit Withdrawal(msg.sender, wad); - } - - function totalSupply() public view returns (uint) { - return address(this).balance; - } - - function approve(address guy, uint wad) public returns (bool) { - allowance[msg.sender][guy] = wad; - emit Approval(msg.sender, guy, wad); - return true; - } - - function transfer(address dst, uint wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { - require(balanceOf[src] >= wad, "Insufficient balance"); - - if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) { - require(allowance[src][msg.sender] >= wad, "Insufficient allowance"); - allowance[src][msg.sender] -= wad; - } - - balanceOf[src] -= wad; - balanceOf[dst] += wad; - - emit Transfer(src, dst, wad); - - return true; - } -} \ No newline at end of file diff --git a/src/interfaces/IRedeemableAsset.sol b/src/interfaces/IRedeemableAsset.sol new file mode 100644 index 000000000..59e96fe9e --- /dev/null +++ b/src/interfaces/IRedeemableAsset.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +interface IRedeemableAsset is IERC20Metadata { + function burn(uint256 amount) external; + function processWithdrawnETH() external payable; +} \ No newline at end of file diff --git a/src/interfaces/IRedemptionAssetsVault.sol b/src/interfaces/IRedemptionAssetsVault.sol new file mode 100644 index 000000000..343b2fb32 --- /dev/null +++ b/src/interfaces/IRedemptionAssetsVault.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +interface IRedemptionAssetsVault { + + // Events + event AssetsDeposited( address indexed asset, address indexed depositor, uint256 amount); + event AssetTransferred(address indexed asset, address indexed redeemer, address indexed to, uint256 amount); + event AssetWithdrawn(address indexed asset, address indexed redeemer, address indexed to, uint256 amount); + + /// @notice Transfers redemption assets to a specified address based on redemption. + /// @dev This is only for INTERNAL USE + /// @param to The address to which the assets will be transferred. + /// @param amount The amount in unit of account + /// @param data Extra data payload for redemption request + function transferRedemptionAssets(address to, uint256 amount, bytes calldata data) external; + + /// @notice Withdraws redemption assets from the queue's balance + /// @param amount The amount in unit of account + function withdrawRedemptionAssets(uint256 amount) external; + + /// @notice Retrieves the current redemption rate for the asset in the unit of account. + /// @return The current redemption rate + function redemptionRate() external view returns (uint256); + + /// @notice Gets the total amount of redemption assets available for withdrawal in the unit of account. + /// @return The available amount of redemption assets + function availableRedemptionAssets() external view returns (uint256); +} diff --git a/src/interfaces/IStakingNode.sol b/src/interfaces/IStakingNode.sol index 403862e03..8dad94408 100644 --- a/src/interfaces/IStakingNode.sol +++ b/src/interfaces/IStakingNode.sol @@ -6,10 +6,11 @@ import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; import {ISignatureUtils} from "lib/eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; struct WithdrawalCompletionParams { uint256 middlewareTimesIndex; - uint amount; + uint256 amount; uint32 withdrawalStartBlock; address delegatedAddress; uint96 nonce; @@ -30,7 +31,7 @@ interface IStakingNode { /// @notice Configuration for contract initialization. struct Init { IStakingNodesManager stakingNodesManager; - uint nodeId; + uint256 nodeId; } function stakingNodesManager() external view returns (IStakingNodesManager); @@ -44,33 +45,47 @@ interface IStakingNode { ) external; function undelegate() external; - function withdrawNonBeaconChainETHBalanceWei() external; - function processDelayedWithdrawals() external; - function implementation() external view returns (address); - function allocateStakedETH(uint amount) external payable; - function getETHBalance() external view returns (uint); - function nodeId() external view returns (uint); + function allocateStakedETH(uint256 amount) external payable; + function deallocateStakedETH(uint256 amount) external payable; + function getETHBalance() external view returns (uint256); + function unverifiedStakedETH() external view returns (uint256); + function nodeId() external view returns (uint256); /// @notice Returns the beaconChainETHStrategy address used by the StakingNode. function beaconChainETHStrategy() external view returns (IStrategy); /** * @notice Verifies the withdrawal credentials and balance of validators. - * @param oracleTimestamp An array of oracle block numbers corresponding to each validator. + * @param beaconTimestamp An array of oracle block numbers corresponding to each validator. * @param stateRootProof An array of state root proofs corresponding to each validator. * @param validatorIndices An array of validator indices. * @param validatorFieldsProofs An array of ValidatorFieldsAndBalanceProofs, containing the merkle proofs for validator fields and balances. * @param validatorFields An array of arrays, each containing the validator fields to be verified. */ function verifyWithdrawalCredentials( - uint64 oracleTimestamp, + uint64 beaconTimestamp, BeaconChainProofs.StateRootProof calldata stateRootProof, uint40[] calldata validatorIndices, bytes[] calldata validatorFieldsProofs, bytes32[][] calldata validatorFields ) external; + function queueWithdrawals( + uint256 sharesAmount + ) external returns (bytes32[] memory fullWithdrawalRoots); + + function completeQueuedWithdrawals( + IDelegationManager.Withdrawal[] memory withdrawals, + uint256[] memory middlewareTimesIndexes + ) external; + function getInitializedVersion() external view returns (uint64); + + function getUnverifiedStakedETH() external view returns (uint256); + function getQueuedSharesAmount() external view returns (uint256); + function getWithdrawnETH() external view returns (uint256); + function startCheckpoint(bool revertIfNoBalance) external; + function initializeV2(uint256 initialUnverifiedStakedETH) external; } diff --git a/src/interfaces/IStakingNodesManager.sol b/src/interfaces/IStakingNodesManager.sol index a6ef118d3..7facc340d 100644 --- a/src/interfaces/IStakingNodesManager.sol +++ b/src/interfaces/IStakingNodesManager.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.24; import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {RewardsType} from "src/interfaces/IRewardsDistributor.sol"; import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IStakingNode} from "src/interfaces/IStakingNode.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; + interface IStakingNodesManager { @@ -23,11 +24,17 @@ interface IStakingNodesManager { uint256 nodeId; } + struct WithdrawalAction { + uint256 nodeId; + uint256 amountToReinvest; + uint256 amountToQueue; + uint256 rewardsAmount; + } + function eigenPodManager() external view returns (IEigenPodManager); function delegationManager() external view returns (IDelegationManager); function strategyManager() external view returns (IStrategyManager); - function delayedWithdrawalRouter() external view returns (IDelayedWithdrawalRouter); function getAllValidators() external view returns (Validator[] memory); function getAllNodes() external view returns (IStakingNode[] memory); function isStakingNodesOperator(address) external view returns (bool); @@ -39,6 +46,16 @@ interface IStakingNodesManager { function nodesLength() external view returns (uint256); function upgradeableBeacon() external returns (UpgradeableBeacon); -} + function totalDeposited() external view returns (uint256); + function processPrincipalWithdrawals( + WithdrawalAction[] memory actions + ) external; + + function redemptionAssetsVault() external returns (IRedemptionAssetsVault); + + function isStakingNodesWithdrawer(address _address) external view returns (bool); + + function nodes(uint256 index) external view returns (IStakingNode); +} diff --git a/src/interfaces/IWithdrawalQueueManager.sol b/src/interfaces/IWithdrawalQueueManager.sol new file mode 100644 index 000000000..bb8cd76fe --- /dev/null +++ b/src/interfaces/IWithdrawalQueueManager.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + + +interface IWithdrawalQueueManager { + + struct WithdrawalRequest { + uint256 amount; + uint256 feeAtRequestTime; + uint256 redemptionRateAtRequestTime; + uint256 creationTimestamp; + bool processed; + bytes data; + } + + struct Finalization { + uint64 startIndex; + uint64 endIndex; + uint96 redemptionRate; + } + + struct WithdrawalClaim { + uint256 tokenId; + uint256 finalizationId; + address receiver; + } + + function requestWithdrawal(uint256 amount) external returns (uint256); + function requestWithdrawal(uint256 amount, bytes calldata data) external returns (uint256); + function claimWithdrawal(WithdrawalClaim memory claim) external; + function finalizeRequestsUpToIndex(uint256 _lastFinalizedIndex) external returns (uint256); +} diff --git a/src/interfaces/IynETH.sol b/src/interfaces/IynETH.sol index 1370af1c8..35624afae 100644 --- a/src/interfaces/IynETH.sol +++ b/src/interfaces/IynETH.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.24; import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {IERC20Permit} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import {IRedeemableAsset} from "src/interfaces/IRedeemableAsset.sol"; +import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; -interface IynETH is IERC20 { + +interface IynETH is IERC20, IRedeemableAsset { function withdrawETH(uint256 ethAmount) external; function processWithdrawnETH() external payable; function receiveRewards() external payable; @@ -15,4 +17,19 @@ interface IynETH is IERC20 { /// @param receiver The address to receive the minted shares. /// @return shares The amount of shares minted for the deposited ETH. function depositETH(address receiver) external payable returns (uint256 shares); + + function previewRedeem(uint256 shares) external view returns (uint256); + + /// @notice Returns the total amount of assets managed by the contract. + /// @return The total amount of assets in wei. + function totalAssets() external view returns (uint256); + + /// @notice Returns the address of the StakingNodesManager contract. + /// @return The address of the StakingNodesManager contract. + function stakingNodesManager() external view returns (IStakingNodesManager); + + /// @notice Simulates the effects of a deposit at the current block, given the amount of assets. + /// @param assets The amount of assets to simulate depositing. + /// @return shares The amount of shares that would be minted. + function previewDeposit(uint256 assets) external view returns (uint256 shares); } diff --git a/src/ynETH.sol b/src/ynETH.sol index e0bb57379..04cd4c1b6 100644 --- a/src/ynETH.sol +++ b/src/ynETH.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.24; import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; -import {IStakingNode} from "src/interfaces/IStakingNode.sol"; import {IynETH} from "src/interfaces/IynETH.sol"; import {ynBase} from "src/ynBase.sol"; @@ -35,9 +34,18 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { error ZeroETH(); error NoDirectETHDeposit(); error CallerNotStakingNodeManager(address expected, address provided); - error NotRewardsDistributor(); + error NotRewardsDistributor(address caller); error InsufficientBalance(); error TransferFailed(); + error CallerNotAuthorized(address caller); + error ZeroShares(); + + + //-------------------------------------------------------------------------------------- + //---------------------------------- ROLES -------------------------------------------- + //-------------------------------------------------------------------------------------- + + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); //-------------------------------------------------------------------------------------- //---------------------------------- VARIABLES --------------------------------------- @@ -53,7 +61,9 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { //---------------------------------- INITIALIZATION ---------------------------------- //-------------------------------------------------------------------------------------- - /// @notice Configuration for contract initialization. + /** + * @notice Configuration for contract initialization. + */ struct Init { address admin; address pauser; @@ -67,9 +77,10 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { _disableInitializers(); } - - /// @notice Initializes the contract. - /// @dev MUST be called during the contract upgrade to set up the proxies state. + /** + * @notice Initializes the contract. + * @dev MUST be called during the contract upgrade to set up the proxies state. + */ function initialize(Init memory init) external notZeroAddress(init.admin) @@ -98,6 +109,7 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { //-------------------------------------------------------------------------------------- //---------------------------------- DEPOSITS --------------------------------------- //-------------------------------------------------------------------------------------- + /** * @notice Allows depositing ETH into the contract in exchange for shares. * @dev Mints shares equivalent to the deposited ETH value and assigns them to the receiver. @@ -118,14 +130,38 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { shares = previewDeposit(assets); + if (shares == 0) { + revert ZeroShares(); + } + _mint(receiver, shares); totalDepositedInPool += assets; emit Deposit(msg.sender, receiver, assets, shares, totalDepositedInPool); } - /// @notice Converts from ynETH to ETH using the current exchange rate. - /// The exchange rate is given by the total supply of ynETH and total ETH controlled by the protocol. + /** + * @notice Calculates the amount of shares to be minted for a given deposit. + * @param assets The amount of assets to be deposited. + * @return The amount of shares to be minted. + */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** + * @notice Converts a given amount of assets to shares. + * @param assets The amount of assets to be converted. + * @return shares The equivalent amount of shares. + */ + function convertToShares(uint256 assets) public view returns (uint256 shares) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** + * @notice Converts from ynETH to ETH using the current exchange rate. + * The exchange rate is given by the total supply of ynETH and total ETH controlled by the protocol. + */ function _convertToShares(uint256 ethAmount, Math.Rounding rounding) internal view returns (uint256) { // 1:1 exchange rate on the first stake. // Use totalSupply to see if this is the boostrap call, not totalAssets @@ -142,57 +178,97 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { ); } - /// @notice Calculates the amount of shares to be minted for a given deposit. - /// @param assets The amount of assets to be deposited. - /// @return The amount of shares to be minted. - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); + //-------------------------------------------------------------------------------------- + //---------------------------------- WITHDRAWALS -------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Calculates the amount of assets that would be redeemed for a given amount of shares at current block + * @param shares The amount of shares to redeem. + * @return assets The equivalent amount of assets. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets) { + return _convertToAssets(shares, Math.Rounding.Floor); } - /// @notice Calculates the total assets controlled by the protocol. - /// @dev This includes both the ETH deposited in the pool awaiting processing and the ETH already sent to validators on the beacon chain. - /// @return total The total amount of ETH in wei. + /** + * @notice Converts a given amount of shares to assets at current block + * @param shares The amount of shares to convert. + * @return assets The equivalent amount of assets. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** + * @dev Internal implementation of {convertToAssets}. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view returns (uint256) { + + uint256 supply = totalSupply(); + + // 1:1 exchange rate on the first stake. + // Use totalSupply to see if this call is made before boostrap call, not totalAssets + if (supply == 0) { + return shares; + } + return Math.mulDiv(shares, totalAssets(), supply, rounding); + } + + function burn(uint256 amount) external onlyRole(BURNER_ROLE) { + _burn(msg.sender, amount); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- ASSETS ------------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Calculates the total assets controlled by the protocol. + * @dev This includes both the ETH deposited in the pool awaiting processing and the ETH already sent to validators on the beacon chain. + * @return total The total amount of ETH in wei. + */ function totalAssets() public view returns (uint256) { uint256 total = 0; // Allocated ETH for deposits pending to be processed. total += totalDepositedInPool; // The total ETH sent to the beacon chain. - total += totalDepositedInValidators(); + total += totalDeposited(); return total; } - /// @notice Calculates the total amount of ETH deposited across all validators. - /// @dev Iterates through all staking nodes to sum up their ETH balances. - /// @return totalDeposited The total amount of ETH deposited in all validators. - function totalDepositedInValidators() internal view returns (uint256) { - - IStakingNode[] memory nodes = stakingNodesManager.getAllNodes(); - uint256 totalDeposited = 0; - for (uint256 i = 0; i < nodes.length; i++) { - totalDeposited += nodes[i].getETHBalance(); - } - return totalDeposited; + /** + * @notice Returns the total amount of ETH deposited across all validators. + * @return totalDeposited The total amount of ETH deposited in all validators. + */ + function totalDeposited() internal view returns (uint256) { + + return stakingNodesManager.totalDeposited(); } //-------------------------------------------------------------------------------------- //---------------------------------- STAKING/UNSTAKING and REWARDS ------------------- //-------------------------------------------------------------------------------------- - /// @notice Receives rewards in ETH and adds them to the total deposited in the pool. - /// @dev Only the rewards distributor contract can call this function. - /// Reverts if called by any address other than the rewards distributor. + /** + * @dev Receives rewards in ETH and adds them to the total deposited in the pool. + * Only the rewards distributor contract can call this function. + * Reverts if called by any address other than the rewards distributor. + */ function receiveRewards() external payable { if (msg.sender != address(rewardsDistributor)) { - revert NotRewardsDistributor(); + revert NotRewardsDistributor(msg.sender); } totalDepositedInPool += msg.value; emit RewardsReceived(msg.value, totalDepositedInPool); } - /// @notice Withdraws a specified amount of ETH from the pool to the Staking Nodes Manager. - /// @dev This function can only be called by the Staking Nodes Manager. - /// @param ethAmount The amount of ETH to withdraw in wei. + /** + * @dev Withdraws a specified amount of ETH from the pool to the Staking Nodes Manager. + * This function can only be called by the Staking Nodes Manager. + * @param ethAmount The amount of ETH to withdraw in wei. + */ function withdrawETH(uint256 ethAmount) public onlyStakingNodesManager override { uint256 currentTotalDepositedInPool = totalDepositedInPool; @@ -214,24 +290,40 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { emit ETHWithdrawn(ethAmount, newTotalDepositedInPool); } - /// @notice Processes ETH that has been withdrawn from the staking nodes and adds it to the pool. - /// @dev This function can only be called by the Staking Nodes Manager. - /// It increases the total deposited in the pool by the amount of ETH sent with the call. - function processWithdrawnETH() public payable onlyStakingNodesManager { + /** + * @dev Processes ETH that has been withdrawn from the staking nodes and adds it to the pool. + * This function can only be called by the Staking Nodes Manager. + * It increases the total deposited in the pool by the amount of ETH sent with the call. + */ + function processWithdrawnETH() public payable { + + // ETH can be returned either by the stakingNodesManager or by the redemptionAssetsVault + if (!(msg.sender == address(stakingNodesManager) + || msg.sender == (address(stakingNodesManager.redemptionAssetsVault())))) { + revert CallerNotAuthorized(msg.sender); + } totalDepositedInPool += msg.value; emit WithdrawnETHProcessed(msg.value, totalDepositedInPool); } - /// @notice Pauses ETH deposits. - /// @dev Can only be called by an account with the PAUSER_ROLE. + //-------------------------------------------------------------------------------------- + //---------------------------------- PAUSING ----------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Pauses ETH deposits. + * @dev Can only be called by an account with the PAUSER_ROLE. + */ function pauseDeposits() external onlyRole(PAUSER_ROLE) { depositsPaused = true; emit DepositETHPausedUpdated(depositsPaused); } - /// @notice Unpauses ETH deposits. - /// @dev Can only be called by an account with the UNPAUSER_ROLE. + /** + * @notice Unpauses ETH deposits. + * @dev Can only be called by an account with the UNPAUSER_ROLE. + */ function unpauseDeposits() external onlyRole(UNPAUSER_ROLE) { depositsPaused = false; emit DepositETHPausedUpdated(depositsPaused); @@ -241,6 +333,9 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { //---------------------------------- MODIFIERS --------------------------------------- //-------------------------------------------------------------------------------------- + /** + * @dev Ensures that the function is only called by the Staking Nodes Manager. + */ modifier onlyStakingNodesManager() { if (msg.sender != address(stakingNodesManager)) { revert CallerNotStakingNodeManager( @@ -251,8 +346,10 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { _; } - /// @notice Ensure that the given address is not the zero address. - /// @param _address The address to check. + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ modifier notZeroAddress(address _address) { if (_address == address(0)) { revert ZeroAddress(); @@ -260,3 +357,4 @@ contract ynETH is IynETH, ynBase, IYnETHEvents { _; } } + diff --git a/src/ynETHRedemptionAssetsVault.sol b/src/ynETHRedemptionAssetsVault.sol new file mode 100644 index 000000000..eeb326d7f --- /dev/null +++ b/src/ynETHRedemptionAssetsVault.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; +import {AccessControlUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import {IynETH} from "src/interfaces/IynETH.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; +import {ETH_ASSET, YNETH_UNIT} from "src/Constants.sol"; + + +contract ynETHRedemptionAssetsVault is IRedemptionAssetsVault, Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { + + //-------------------------------------------------------------------------------------- + //---------------------------------- ERRORS ------------------------------------------ + //-------------------------------------------------------------------------------------- + + error TransferFailed(uint256 amount, address destination); + error ZeroAddress(); + error InsufficientAssetBalance(address asset, uint256 requestedAmount, uint256 balance); + error ContractPaused(); + error NotRedeemer(address caller); + + //-------------------------------------------------------------------------------------- + //---------------------------------- ROLES ------------------------------------------- + //-------------------------------------------------------------------------------------- + + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE"); + + //-------------------------------------------------------------------------------------- + //---------------------------------- VARIABLES --------------------------------------- + //-------------------------------------------------------------------------------------- + + IynETH public ynETH; + bool public paused; + address public redeemer; + + // Initializer with Init struct and roles + struct Init { + address admin; + address redeemer; + IynETH ynETH; + } + + function initialize(Init memory init) + external + notZeroAddress(init.admin) + notZeroAddress(init.redeemer) + notZeroAddress(address(init.ynETH)) + initializer { + __AccessControl_init(); + _grantRole(DEFAULT_ADMIN_ROLE, init.admin); + _grantRole(PAUSER_ROLE, init.admin); + _grantRole(UNPAUSER_ROLE, init.admin); + + redeemer = init.redeemer; + ynETH = init.ynETH; + paused = false; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- REDEMPTION -------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Accepts incoming ETH deposits. + */ + receive() external payable { + emit AssetsDeposited(msg.sender, ETH_ASSET, msg.value); + } + + /** + * @notice Calculates the current redemption rate of ynETH to ETH. + * @return The current redemption rate as a uint256. + */ + function redemptionRate() public view returns (uint256) { + return ynETH.previewRedeem(YNETH_UNIT); + } + + /** + * @notice Returns the total amount of ETH available for redemption. + * @return The available ETH balance as a uint256. + */ + function availableRedemptionAssets() public view returns (uint256) { + return address(this).balance; + } + + /** + * @notice Transfers a specified amount of redemption assets to a given address. + * @param to The recipient address of the assets. + * @param amount The amount of assets to transfer. + * @dev Requires the caller to be the redeemer and the contract to not be paused. + */ + function transferRedemptionAssets(address to, uint256 amount, bytes calldata /* data */) public onlyRedeemer whenNotPaused nonReentrant { + uint256 balance = availableRedemptionAssets(); + if (balance < amount) { + revert InsufficientAssetBalance(ETH_ASSET, amount, balance); + } + + (bool success, ) = payable(to).call{value: amount}(""); + if (!success) { + revert TransferFailed(amount, to); + } + emit AssetTransferred(ETH_ASSET, msg.sender, to, amount); + } + + /** + * @notice Withdraws a specified amount of redemption assets and processes them through ynETH. + * @param amount The amount of ETH to withdraw and process. + * @dev Requires the caller to be the redeemer and the contract to not be paused. + */ + function withdrawRedemptionAssets(uint256 amount) public onlyRedeemer whenNotPaused nonReentrant { + ynETH.processWithdrawnETH{ value: amount }(); + emit AssetWithdrawn(ETH_ASSET, msg.sender, address(ynETH), amount); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- MODIFIERS --------------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Ensure that the given address is not the zero address. + * @param _address The address to check. + */ + modifier notZeroAddress(address _address) { + if (_address == address(0)) { + revert ZeroAddress(); + } + _; + } + + /** + * @notice Checks if the contract is not paused. + */ + modifier whenNotPaused() { + if (paused) { + revert ContractPaused(); + } + _; + } + + /** + * @notice Ensures that the caller has the REDEEMER_ROLE. + */ + modifier onlyRedeemer() { + if (msg.sender != redeemer) { + revert NotRedeemer(msg.sender); + } + _; + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- PAUSE FUNCTIONS --------------------------------- + //-------------------------------------------------------------------------------------- + + /** + * @notice Pauses the contract, preventing certain actions. + */ + function pause() external onlyRole(PAUSER_ROLE) { + paused = true; + } + + /** + * @notice Unpauses the contract, allowing certain actions. + */ + function unpause() external onlyRole(UNPAUSER_ROLE) { + paused = false; + } +} diff --git a/test/data/ValidatorFieldsProof_1293592_8654000.json b/test/data/ValidatorFieldsProof_1293592_8654000.json deleted file mode 100644 index c58320809..000000000 --- a/test/data/ValidatorFieldsProof_1293592_8654000.json +++ /dev/null @@ -1 +0,0 @@ -{"latestBlockHeaderRoot":"0x7b1b37d586b7a4646dd7dcf375d2f5fce0f84fa1c82323efe9472ae54055f21f","StateRootAgainstLatestBlockHeaderProof":["0x906f23150765deb207aa5f1222f04f1b94f0e73fb1c3d422c1a593d2fcd79fca","0x5c39435a823b0696721434fef3106ca5b7ae446c94714d03b68d46abe7689a68","0x9da36d89fd61007e8b950779fd9ad981cced5158515e1570068c195080b97bae"],"beaconStateRoot":"0xff0d6990c14e088df9b03daf05e38eb13028d6669c2c75511e7b9049b97f6cd2","validatorIndex":1293592,"WithdrawalCredentialProof":["0xb07fca56099cb1a1f9b7e2c82b1d86db90399188e75a6625a88f28a48a68100e","0xd4158c354e2f5f49567880ee17057a6ac453a6a7a0d5438c853e4a9580014ca8","0x8467e406a5d378d89ec4e5891778c3cff8daea52a59019a52892cdd27555efb8","0xa75187ac327ff33c6ee7ab6e0daaf06333a6a673bac55a39c0dfdea167931ecd","0x94e81b94ab8beb52224dae28227c5ca29f68b4ae679719871c01de1fecd51160","0x6ce9fd2a64f831a1d09d1650917f73a6631aee7e49af89024740f153dccf45cc","0xefecf5b60751f3e61564d90f6e5f63706796c6f4780f49fba056046874409136","0xf5d8e3fa2e08ea7e38198cd448fd56a3fa8f069a926b18e7c2ed81d2b94c50d8","0x2f0aa23e97bcc200cad5703d9595c184ea8a988a01e3d7d71ba837a76b6aa79b","0x9d477fd3210772be2236dad8a0362d937857c4630eb6e44e6e5a732f5b64b743","0x10109b553073e31facdd818130cf6efac18fcd7282c4c650f433c56cfea66f19","0x9cd76734e8d54521f23730745602c9a7da93ea3ec9266dfc836f8d975a557c9a","0xbcac615f0eac899419761dd35544ae7b624325616ddab91f426c7e98efd91332","0x9840c6d12f6151e5e2847fc049dd2d36c98a0b7cbea107ae2ed8df0c1ba15dc7","0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784","0xcc7759d8db3b29d750d44e2489cf34abbdde5ecc9ee3a5ea5de9d980fff12ed0","0xfc5f46d2acb903aa7b1ecdaf6af093675462670c810d29fc391219dcf9c31910","0x59c202045f7ae8eaccb8eba18a05bd6aeecaa7558d007cbd832f1d06b3aa2b6e","0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0x74dd227c1f73be4c6b815a50b6d3bf1f09a631c664ed255551dc8daf2ed1e272","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x5bbf130000000000000000000000000000000000000000000000000000000000","0xbba8140000000000000000000000000000000000000000000000000000000000","0x009904b6735cdf50af55944bc1f54b1cc7512003ae342224b4e5540597fab994","0xda576afa280012412f97a6f00120f64af8526be7782a0d8477a669ecc55f239f","0x8f245fc027af1c1c6d8d163dd1efac2335c9b0c841ff81718d943fa6dc8f719d","0xeafa7617e535dcd46d18ba89da344184cb066399f6776de02f404fa72d080b5b"],"ValidatorFields":["0x032672698a5d1490aff8adc7a89d1cbc356be9ed356941900a51f9521df880ea","0x010000000000000000000000169eb59289d14642dd285def3d75f63aea3218d2","0x00ca9a3b00000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"]} \ No newline at end of file diff --git a/test/data/ValidatorFieldsProof_1293592_8746783.json b/test/data/ValidatorFieldsProof_1293592_8746783.json deleted file mode 100644 index 141b7da2b..000000000 --- a/test/data/ValidatorFieldsProof_1293592_8746783.json +++ /dev/null @@ -1 +0,0 @@ -{"latestBlockHeaderRoot":"0xd180de7c35181730a04925bec5d66b5dca72c227e83fbe11c6bfe72f26cea916", "StateRootAgainstLatestBlockHeaderProof":["0x10f8e7bb3dcd4baa923f7c5c5e7373eb1645e52fb733886ffdf7ffbad70843d0","0x74bac64887c09222633555f73be2b22ff5bff91e8c4424e61f2599e7afd34600","0x598c21f1a83e0d657ff22bea9e835a41f41a2147e053fd381a2ddbdd9da8c0a3"],"beaconStateRoot":"0x2cc17e5ba0e24a0525d8faf0d17f5154735e74e598746b8f202d939395a1ec70","validatorIndex":1293592,"WithdrawalCredentialProof":["0x179486bdc45c6165c3f4cdbe7c2b01da5801872de8e4acc5a83b1abd461bdc83","0x138d2654a9feaeae4c739444c29c92db00b9169908dcad7729228796f5ecb881","0xff7e86c6a67b8a6903079a1308bef388d5d4b61a1ec227ec8080e47d5e59e807","0x2881f0793d08159bed9b6589d80c34fba0efd3f4cbe2433a0db6165e060ffffc","0xbc03229fee854777c54ead163727c752c65da0090e016145f1b5324dbfef8a3e","0x47fa4e9f9b0fd656d9734f0dc361d0b923cbad273e6c9684c7c3f6ddfaac310b","0x8225afc955fe51f21dac1c08145f422371f23322d4cf33778dc1f334c1f8423e","0xf500b4c0e7dce3446150712c805ee2043bbd3bfc5476d5369719598aa890f30f","0xa970ddbc1461f77b33e6d217a03e9423b5bacd5217681f68bbb1b5576797e726","0xc67e3a844e1a33824df15ce206543ff62887be7e228d247432ceb84cb1811cac","0x832fcf4462dad1f20df0e012890c246fc6ebd1902c124439cc06eb5fb0f47071","0x8b7ce28a2483271da908a1ea961e0231e556714e6077001019a6e12bcd3b997d","0xf08982cdfb4eb7f6d14752910d4039807e61287f5084048bb610e9c2ae46fb56","0xc9eaa2a29d8f3e3e91b5cca1cc6ae875932c30305bea06eddd152e573c133f88","0x78fa02ffb1ca6d5ef9fda534bc3a26664b806b6a73513194912b994ff0911a90","0xb952ade06abd18788bf015f52c14bfd21563b0d3c7f6fed9714f9d4990693608","0xc3dba7c55bce5552613417b2e84a7ba76f16bcee2279e1b111702d543b94df8b","0x70ee1a586ee18511c02c9af94fb156f53822f7ce34543fe8b787ae670e8da115","0xbda70b70e873b10f288a8ac1844a51519f23dafe770e31c0b905fec2cb740571","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0x93b52bdacf8f202fd99b6090d5d5d1477179df7731ffa812ca33f709c5f5d668","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0xba40140000000000000000000000000000000000000000000000000000000000","0x9e47150000000000000000000000000000000000000000000000000000000000","0x54c7ec26c738a57a762c7f089fd6ac7825044422f69e5badef07160d956f3a02","0x308a5ff4a4949bf2d2a6ce2a6226fd4a98d7c6be2b3ced9e416106e82cec0a10","0xef66a1cadbe771dc0ef3b2be81c0507c7b8e7259f7f097ea64d1fc2d138b4b82","0xc9002c9c1fbeaf90a1ffc5daaef51f065b612d9a66fcd1b4cd70acc9a8a13026"],"ValidatorFields":["0x032672698a5d1490aff8adc7a89d1cbc356be9ed356941900a51f9521df880ea","0x010000000000000000000000169eb59289d14642dd285def3d75f63aea3218d2","0x0040597307000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x2222040000000000000000000000000000000000000000000000000000000000","0x8823040000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"]} \ No newline at end of file diff --git a/test/data/mainnet_withdrawal_credential_proof_1285801.json b/test/data/mainnet_withdrawal_credential_proof_1285801.json deleted file mode 100644 index da899c893..000000000 --- a/test/data/mainnet_withdrawal_credential_proof_1285801.json +++ /dev/null @@ -1 +0,0 @@ -{"StateRootAgainstLatestBlockHeaderProof":["0x929d2d3b873e9a6e48c20ee50170f2c6ed99afca3af110ad2ad77f5fd6c24fad","0xb3271e1fa46b9dcabc611c25034ef1f82c44a3cfc9ecfdff5fe9e91aab989324","0x5b1062b878fa5a332d523815ed512402feccbb7b29ce66351e906780e62080bd"],"beaconStateRoot":"0x09c1536ff7e3f60a641c7ca4d5b0cad0258230f6c41fbdc4542f88ec125f3f83","validatorIndex":1285801,"WithdrawalCredentialProof":["0xec5d45628347d887cfdf81c37c476aca898b8b3b7d6a1e7ceffa0e044989e1a5","0x00e8db055ba65c2575676f3f0679e3604ce9081158cc22bb833f63e992539523","0xda2c87d613b0495e0b04c93cc9871405c47b3d0ec1a7549312cf4d3eaa0121bc","0x4d96a5d7791ac396e5f03c9b4febfd01cd37e4a563de6946716687c9a75c29e1","0xa76fd465e81307360961f517e4c4107a27d3af33a1a190d4bf549d8593a1e398","0xcae94f12d9a481222633d4dee2281cccc7f5a8436280ecc61db689e24ebf8a9c","0x2377527b45d945b518d6236744aa33cad9da16576cc7f5ccc7e5bf33a0cfe4b8","0x05300957c34438b4152fd830b87e4f46c43a2be1a247976d8d05474e65591820","0x20b6cebf3d6f7ab7735ddbeadf6aeaa5cbd7c69c1c0c045273fa3bc1ee283e36","0x57f71c1c12fe674592599c00040377a84bfa5c6cea6c287211e2f31ae9001111","0xbb12b5d89b78d6bf4bb2ff638e4e2686f2c32170c40318d2da3ff990a638117b","0xa68e1a52e2bb226bd666f08eb58b45207252be1d367c24cc73754797e883ea42","0x9048fab48cd08c2ad219cdd334a27fed011d692a2e64b629fe36193726d885c4","0x440e8cf1d2a35c267b9f7ece58b72da215ee0118bb100c8c603879345f140a1e","0x905fdc193010f46f183c0ba9793d951e1f0fe72e21a911c83bf5e6951cba4c87","0x9ebc7da8b11286d34959862805bb7a07c444bb9c5024764c8aff1bd2c2776e9c","0xc0220159dbdaf00269cc8da89d21034f5d136a3686c7cba6b1fa0d133ff510a1","0xf384afef417a794887347216a18928c301d2afa4b1a9e743c81ebfd8f89ed26d","0x9eec16bf77dd63f57872f703378c78b5a7495c1407340b257437802be91864ce","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0xb8f0f831926b9d04736461d37c3cfbe361c2559027804d02a3dd852021e90f85","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0xdb90140000000000000000000000000000000000000000000000000000000000","0xcfa5150000000000000000000000000000000000000000000000000000000000","0x82cd5f5217557f4fe20e751cea9997884183169c6e774272b21ddaba7c442b11","0x94bb1c4a154a394f311c019b5505846c89ed71446cb72d07b8f155a947d842c2","0x8c8bcca69a62b82d793940de78666e82402d07168cd0369586c39c6e9bde679f","0xca5714e6b144fed96bbd6a959b24cf14771bc59448ae370d4b026836b9275d0e"],"ValidatorFields":["0x1eefa575cc42686b3ae4a0c04cc68f6b09bb4bb50236aef839d765343ae1705f","0x010000000000000000000000c98d5fec02b55160104ee1ca85ae7340e8588203","0x0040597307000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x231f040000000000000000000000000000000000000000000000000000000000","0x9a1f040000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"]} \ No newline at end of file diff --git a/test/integration/IntegrationBaseTest.sol b/test/integration/IntegrationBaseTest.sol index ff829693c..e25b56ec2 100644 --- a/test/integration/IntegrationBaseTest.sol +++ b/test/integration/IntegrationBaseTest.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.24; import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {IERC4626} from "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; +// import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import {IDepositContract} from "src/external/ethereum/IDepositContract.sol"; import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; @@ -28,6 +28,11 @@ import {StakingNode} from "src/StakingNode.sol"; import {Utils} from "script/Utils.sol"; import {ActorAddresses} from "script/Actors.sol"; import {TestAssetUtils} from "test/utils/TestAssetUtils.sol"; +import {WithdrawalQueueManager} from "src/WithdrawalQueueManager.sol"; +import {ynETHRedemptionAssetsVault} from "src/ynETHRedemptionAssetsVault.sol"; +import {IRedeemableAsset} from "src/interfaces/IRedeemableAsset.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; +import {BeaconChainMock, BeaconChainProofs, CheckpointProofs, CredentialProofs, EigenPodManager} from "lib/eigenlayer-contracts/src/test/integration/mocks/BeaconChainMock.t.sol"; contract IntegrationBaseTest is Test, Utils { @@ -37,6 +42,7 @@ contract IntegrationBaseTest is Test, Utils { bytes constant TWO_PUBLIC_KEY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; bytes constant ZERO_SIGNATURE = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; bytes32 constant ZERO_DEPOSIT_ROOT = bytes32(0); + uint64 public constant GENESIS_TIME_LOCAL = 1 hours * 12; // Utils ContractAddresses public contractAddresses; @@ -51,6 +57,10 @@ contract IntegrationBaseTest is Test, Utils { RewardsReceiver public consensusLayerReceiver; RewardsDistributor public rewardsDistributor; + // Withdrawals + WithdrawalQueueManager public ynETHWithdrawalQueueManager; + ynETHRedemptionAssetsVault public ynETHRedemptionAssetsVaultInstance; + // Staking StakingNodesManager public stakingNodesManager; StakingNode public stakingNodeImplementation; @@ -61,7 +71,7 @@ contract IntegrationBaseTest is Test, Utils { // Eigen IEigenPodManager public eigenPodManager; IDelegationManager public delegationManager; - IDelayedWithdrawalRouter public delayedWithdrawalRouter; + // IDelayedWithdrawalRouter public delayedWithdrawalRouter; IStrategyManager public strategyManager; // Ethereum @@ -69,6 +79,8 @@ contract IntegrationBaseTest is Test, Utils { address public transferEnabledEOA; + BeaconChainMock public beaconChain; + function setUp() public virtual { @@ -86,6 +98,7 @@ contract IntegrationBaseTest is Test, Utils { setupStakingNodesManager(); setupYnETH(); setupUtils(); + setupWithdrawalQueueManager(); } function setupYnETHPoxies() public { @@ -150,14 +163,16 @@ contract IntegrationBaseTest is Test, Utils { function setupEthereum() public { depositContractEth2 = IDepositContract(chainAddresses.ethereum.DEPOSIT_2_ADDRESS); + vm.warp(GENESIS_TIME_LOCAL); + beaconChain = new BeaconChainMock(EigenPodManager(address(eigenPodManager)), GENESIS_TIME_LOCAL); } function setupEigenLayer() public { - delayedWithdrawalRouter = IDelayedWithdrawalRouter(vm.addr(6)); + // delayedWithdrawalRouter = IDelayedWithdrawalRouter(vm.addr(6)); strategyManager = IStrategyManager(vm.addr(7)); eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS); delegationManager = IDelegationManager(chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS); - delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); // Assuming DEPOSIT_2_ADDRESS is used for DelayedWithdrawalRouter + // delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); // Assuming DEPOSIT_2_ADDRESS is used for DelayedWithdrawalRouter strategyManager = IStrategyManager(chainAddresses.eigenlayer.STRATEGY_MANAGER_ADDRESS); } @@ -214,7 +229,7 @@ contract IntegrationBaseTest is Test, Utils { ynETH: IynETH(address(yneth)), eigenPodManager: eigenPodManager, delegationManager: delegationManager, - delayedWithdrawalRouter: delayedWithdrawalRouter, + // delayedWithdrawalRouter: delayedWithdrawalRouter, strategyManager: strategyManager, rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), stakingNodeCreatorRole: actors.ops.STAKING_NODE_CREATOR @@ -224,5 +239,100 @@ contract IntegrationBaseTest is Test, Utils { vm.prank(actors.admin.STAKING_ADMIN); // StakingNodesManager is the only contract that can register a staking node implementation contract stakingNodesManager.registerStakingNodeImplementationContract(address(stakingNodeImplementation)); } -} + function setupWithdrawalQueueManager() public { + + TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( + address(new ynETHRedemptionAssetsVault()), + actors.admin.PROXY_ADMIN_OWNER, + "" + ); + ynETHRedemptionAssetsVaultInstance = ynETHRedemptionAssetsVault(payable(address(_proxy))); + + _proxy = new TransparentUpgradeableProxy( + address(new WithdrawalQueueManager()), + actors.admin.PROXY_ADMIN_OWNER, + "" + ); + ynETHWithdrawalQueueManager = WithdrawalQueueManager(address(_proxy)); + + ynETHRedemptionAssetsVault.Init memory _vaultInit = ynETHRedemptionAssetsVault.Init({ + admin: actors.admin.PROXY_ADMIN_OWNER, + redeemer: address(ynETHWithdrawalQueueManager), + ynETH: IynETH(address(yneth)) + }); + ynETHRedemptionAssetsVaultInstance.initialize(_vaultInit); + + WithdrawalQueueManager.Init memory _managerInit = WithdrawalQueueManager.Init({ + name: "ynETH Withdrawal Manager", + symbol: "ynETHWM", + redeemableAsset: IRedeemableAsset(address(yneth)), + redemptionAssetsVault: IRedemptionAssetsVault(address(ynETHRedemptionAssetsVaultInstance)), + admin: actors.admin.PROXY_ADMIN_OWNER, + withdrawalQueueAdmin: actors.ops.WITHDRAWAL_MANAGER, + redemptionAssetWithdrawer: actors.ops.REDEMPTION_ASSET_WITHDRAWER, + requestFinalizer: actors.ops.REQUEST_FINALIZER, + withdrawalFee: 500, // 0.05% + feeReceiver: actors.admin.FEE_RECEIVER + }); + ynETHWithdrawalQueueManager.initialize(_managerInit); + + vm.startPrank(actors.admin.ADMIN); + yneth.grantRole(yneth.BURNER_ROLE(), address(ynETHWithdrawalQueueManager)); + vm.stopPrank(); + + // Initialize V2 of StakingNodesManager + vm.prank(actors.admin.ADMIN); + stakingNodesManager.initializeV2( + StakingNodesManager.Init2({ + redemptionAssetsVault: ynETHRedemptionAssetsVaultInstance, + withdrawalManager: actors.ops.WITHDRAWAL_MANAGER, + stakingNodesWithdrawer: actors.ops.STAKING_NODES_WITHDRAWER + }) + ); + } + + //-------------------------------------------------------------------------------------- + //---------------------------------- UTILITY ----------------------------------------- + //-------------------------------------------------------------------------------------- + + function createValidators(uint256[] memory nodeIds, uint256 count) public returns (uint40[] memory) { + uint40[] memory validatorIndices = new uint40[](count * nodeIds.length); + uint256 index = 0; + + for (uint256 j = 0; j < nodeIds.length; j++) { + bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(nodeIds[j]); + + for (uint256 i = 0; i < count; i++) { + validatorIndices[index] = beaconChain.newValidator{value: 32 ether}(withdrawalCredentials); + index++; + } + } + return validatorIndices; + } + + function registerValidators(uint256[] memory validatorNodeIds) public { + IStakingNodesManager.ValidatorData[] memory validatorData = new IStakingNodesManager.ValidatorData[](validatorNodeIds.length); + + for (uint256 i = 0; i < validatorNodeIds.length; i++) { + bytes memory publicKey = abi.encodePacked(uint256(i)); + publicKey = bytes.concat(publicKey, new bytes(ZERO_PUBLIC_KEY.length - publicKey.length)); + validatorData[i] = IStakingNodesManager.ValidatorData({ + publicKey: publicKey, + signature: ZERO_SIGNATURE, + nodeId: validatorNodeIds[i], + depositDataRoot: bytes32(0) + }); + } + + for (uint256 i = 0; i < validatorData.length; i++) { + uint256 amount = 32 ether; + bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(validatorData[i].nodeId); + bytes32 depositDataRoot = stakingNodesManager.generateDepositRoot(validatorData[i].publicKey, validatorData[i].signature, withdrawalCredentials, amount); + validatorData[i].depositDataRoot = depositDataRoot; + } + + vm.prank(actors.ops.VALIDATOR_MANAGER); + stakingNodesManager.registerValidators(validatorData); + } +} diff --git a/test/integration/M3/Base.t.sol b/test/integration/M3/Base.t.sol new file mode 100644 index 000000000..1e4bf6f2d --- /dev/null +++ b/test/integration/M3/Base.t.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; +import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {BeaconChainMock, BeaconChainProofs, CheckpointProofs, CredentialProofs, EigenPodManager} from "lib/eigenlayer-contracts/src/test/integration/mocks/BeaconChainMock.t.sol"; + +import {Utils} from "../../../script/Utils.sol"; +import {ContractAddresses} from "../../../script/ContractAddresses.sol"; +import {ActorAddresses} from "../../../script/Actors.sol"; + +import {IDepositContract} from "../../../src/external/ethereum/IDepositContract.sol"; + +import {IRedeemableAsset} from "../../../src/interfaces/IRedeemableAsset.sol"; +import {IRedemptionAssetsVault} from "../../../src/interfaces/IRedemptionAssetsVault.sol"; +import {IynETH} from "../../../src/interfaces/IynETH.sol"; + +import {ynETH} from "../../../src/ynETH.sol"; +import {StakingNodesManager, IStakingNodesManager} from "../../../src/StakingNodesManager.sol"; +import {StakingNode} from "../../../src/StakingNode.sol"; +import {RewardsReceiver} from "../../../src/RewardsReceiver.sol"; +import {RewardsDistributor} from "../../../src/RewardsDistributor.sol"; +import {StakingNode} from "../../../src/StakingNode.sol"; +import {WithdrawalQueueManager} from "../../../src/WithdrawalQueueManager.sol"; +import {ynETHRedemptionAssetsVault} from "../../../src/ynETHRedemptionAssetsVault.sol"; +import {IStakingNode} from "../../../src/interfaces/IStakingNodesManager.sol"; +import {WithdrawalsProcessor} from "src/WithdrawalsProcessor.sol"; + +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +contract Base is Test, Utils { + + bytes public constant ZERO_SIGNATURE = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + bytes constant ZERO_PUBLIC_KEY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + // Utils + ContractAddresses public contractAddresses; + ContractAddresses.ChainAddresses public chainAddresses; + ActorAddresses public actorAddresses; + ActorAddresses.Actors public actors; + + // Rewards + RewardsReceiver public executionLayerReceiver; + RewardsReceiver public consensusLayerReceiver; + RewardsDistributor public rewardsDistributor; + + // Staking + StakingNodesManager public stakingNodesManager; + StakingNode public stakingNodeImplementation; + + // Assets + ynETH public yneth; + + // Withdrawals + WithdrawalQueueManager public ynETHWithdrawalQueueManager; + ynETHRedemptionAssetsVault public ynETHRedemptionAssetsVaultInstance; + WithdrawalsProcessor public withdrawalsProcessor; + + // EigenLayer + IEigenPodManager public eigenPodManager; + IDelegationManager public delegationManager; + + // Mock Contracts to deploy + BeaconChainMock public beaconChain; + + // Ethereum + IDepositContract public depositContractEth2; + + uint64 public constant GENESIS_TIME_LOCAL = 1 hours * 12; + + function setUp() public virtual { + assignContracts(); + + // Roles are granted here just for testing purposes. + // On Mainnet only WithdrawalsProcessor has permission to run this, but the system is designed to run + // them separately as well if needed. + // Grant roles on StakingNodesManager for mainnet only + if (block.chainid == 1) { // Mainnet chain ID + vm.startPrank(actors.admin.ADMIN); + stakingNodesManager.grantRole(stakingNodesManager.WITHDRAWAL_MANAGER_ROLE(), actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.grantRole(stakingNodesManager.STAKING_NODES_WITHDRAWER_ROLE(), actors.ops.STAKING_NODES_WITHDRAWER); + vm.stopPrank(); + } + } + + function assignContracts() internal { + contractAddresses = new ContractAddresses(); + chainAddresses = contractAddresses.getChainAddresses(block.chainid); + actorAddresses = new ActorAddresses(); + actors = actorAddresses.getActors(block.chainid); + + // assign YieldNest addresses + { + yneth = ynETH(payable(chainAddresses.yn.YNETH_ADDRESS)); + stakingNodesManager = StakingNodesManager(payable(chainAddresses.yn.STAKING_NODES_MANAGER_ADDRESS)); + rewardsDistributor = RewardsDistributor(payable(chainAddresses.yn.REWARDS_DISTRIBUTOR_ADDRESS)); + executionLayerReceiver = RewardsReceiver(payable(chainAddresses.yn.EXECUTION_LAYER_RECEIVER_ADDRESS)); + consensusLayerReceiver = RewardsReceiver(payable(chainAddresses.yn.CONSENSUS_LAYER_RECEIVER_ADDRESS)); + ynETHWithdrawalQueueManager = WithdrawalQueueManager(payable(chainAddresses.yn.WITHDRAWAL_QUEUE_MANAGER_ADDRESS)); + ynETHRedemptionAssetsVaultInstance = ynETHRedemptionAssetsVault(payable(chainAddresses.yn.YNETH_REDEMPTION_ASSETS_VAULT_ADDRESS)); + withdrawalsProcessor = WithdrawalsProcessor(payable(chainAddresses.yn.WITHDRAWALS_PROCESSOR_ADDRESS)); + } + + // assign Ethereum addresses + { + depositContractEth2 = IDepositContract(chainAddresses.ethereum.DEPOSIT_2_ADDRESS); + } + + // assign Eigenlayer addresses + { + eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS); + delegationManager = IDelegationManager(chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS); + } + + // deploy EigenLayer mocks + { + vm.warp(GENESIS_TIME_LOCAL); + beaconChain = new BeaconChainMock(EigenPodManager(address(eigenPodManager)), GENESIS_TIME_LOCAL); + } + } + + function createValidators(uint256[] memory nodeIds, uint256 count) public returns (uint40[] memory) { + uint40[] memory validatorIndices = new uint40[](count * nodeIds.length); + uint256 index = 0; + + for (uint256 j = 0; j < nodeIds.length; j++) { + bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(nodeIds[j]); + + for (uint256 i = 0; i < count; i++) { + validatorIndices[index] = beaconChain.newValidator{value: 32 ether}(withdrawalCredentials); + index++; + } + } + return validatorIndices; + } + + function registerValidators(uint256[] memory validatorNodeIds) public { + IStakingNodesManager.ValidatorData[] memory validatorData = new IStakingNodesManager.ValidatorData[](validatorNodeIds.length); + + for (uint256 i = 0; i < validatorNodeIds.length; i++) { + bytes memory publicKey = abi.encodePacked(uint256(i)); + publicKey = bytes.concat(publicKey, new bytes(ZERO_PUBLIC_KEY.length - publicKey.length)); + validatorData[i] = IStakingNodesManager.ValidatorData({ + publicKey: publicKey, + signature: ZERO_SIGNATURE, + nodeId: validatorNodeIds[i], + depositDataRoot: bytes32(0) + }); + } + + for (uint256 i = 0; i < validatorData.length; i++) { + uint256 amount = 32 ether; + bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(validatorData[i].nodeId); + bytes32 depositDataRoot = stakingNodesManager.generateDepositRoot(validatorData[i].publicKey, validatorData[i].signature, withdrawalCredentials, amount); + validatorData[i].depositDataRoot = depositDataRoot; + } + + vm.prank(actors.ops.VALIDATOR_MANAGER); + stakingNodesManager.registerValidators(validatorData); + } + + function runSystemStateInvariants( + uint256 previousTotalAssets, + uint256 previousTotalSupply, + uint256[] memory previousStakingNodeBalances + ) public { + assertEq(yneth.totalAssets(), previousTotalAssets, "Total assets integrity check failed"); + assertEq(yneth.totalSupply(), previousTotalSupply, "Share mint integrity check failed"); + + assertEq( + previousStakingNodeBalances.length, + stakingNodesManager.nodesLength(), + "Number of staking nodes changed after upgrade" + ); + for (uint i = 0; i < previousStakingNodeBalances.length; i++) { + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(i); + uint256 currentStakingNodeBalance = stakingNodeInstance.getETHBalance(); + assertEq( + currentStakingNodeBalance, previousStakingNodeBalances[i], + string.concat("Staking node balance integrity check failed for node ID: ", vm.toString(i)) + ); + } + } + + struct UpgradeState { + uint256 totalAssets; + uint256 totalSupply; + uint256[] stakingNodeBalances; + uint256 stakingNodesManagerTotalDeposited; + uint256 previewDepositAmount; + address ynETHStakingNodesManager; + address ynETHRewardsDistributor; + address stakingNodesManagerYnETH; + address stakingNodesManagerRewardsDistributor; + address rewardsDistributorYnETH; + } + + function captureUpgradeState() public view returns (UpgradeState memory) { + return UpgradeState({ + totalAssets: yneth.totalAssets(), + totalSupply: yneth.totalSupply(), + stakingNodesManagerTotalDeposited: 0, // temp value since N/A in previous deployment + stakingNodeBalances: getAllStakingNodeBalances(), + previewDepositAmount: yneth.previewDeposit(1 ether), + ynETHStakingNodesManager: address(yneth.stakingNodesManager()), + ynETHRewardsDistributor: address(yneth.rewardsDistributor()), + stakingNodesManagerYnETH: address(stakingNodesManager.ynETH()), + stakingNodesManagerRewardsDistributor: address(stakingNodesManager.rewardsDistributor()), + rewardsDistributorYnETH: address(rewardsDistributor.ynETH()) + }); + } + + function runUpgradeIntegrityInvariants(UpgradeState memory preUpgradeState) public { + // Check system state invariants + runSystemStateInvariants( + preUpgradeState.totalAssets, + preUpgradeState.totalSupply, + preUpgradeState.stakingNodeBalances + ); + + assertEq( + stakingNodesManager.totalDeposited(), + preUpgradeState.stakingNodesManagerTotalDeposited, + "StakingNodesManager totalDeposited changed after upgrade" + ); + + // Check previewDeposit stays the same + assertEq( + yneth.previewDeposit(1 ether), + preUpgradeState.previewDepositAmount, + "previewDeposit amount changed after upgrade" + ); + + // Check ynETH dependencies stay the same + assertEq( + address(yneth.stakingNodesManager()), + preUpgradeState.ynETHStakingNodesManager, + "ynETH stakingNodesManager changed after upgrade" + ); + assertEq( + address(yneth.rewardsDistributor()), + preUpgradeState.ynETHRewardsDistributor, + "ynETH rewardsDistributor changed after upgrade" + ); + + // Check StakingNodesManager dependencies stay the same + assertEq( + address(stakingNodesManager.ynETH()), + preUpgradeState.stakingNodesManagerYnETH, + "StakingNodesManager ynETH changed after upgrade" + ); + assertEq( + address(stakingNodesManager.rewardsDistributor()), + preUpgradeState.stakingNodesManagerRewardsDistributor, + "StakingNodesManager rewardsDistributor changed after upgrade" + ); + + // Check RewardsDistributor dependencies stay the same + assertEq( + address(rewardsDistributor.ynETH()), + preUpgradeState.rewardsDistributorYnETH, + "RewardsDistributor ynETH changed after upgrade" + ); + } + + function getAllStakingNodeBalances() public view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](stakingNodesManager.nodesLength()); + for (uint256 i = 0; i < stakingNodesManager.nodesLength(); i++) { + IStakingNode stakingNode = stakingNodesManager.nodes(i); + balances[i] = stakingNode.getETHBalance(); + } + return balances; + } + + function logStakingNodeBalancesAndShares(UpgradeState memory preUpgradeState) internal { + // Print StakingNode balance before upgrade + console.log("StakingNode balance before upgrade:"); + for (uint256 i = 0; i < preUpgradeState.stakingNodeBalances.length; i++) { + console.log("Node", i, ":", preUpgradeState.stakingNodeBalances[i]); + } + + // Print current StakingNode balance after upgrade + console.log("StakingNode balance after upgrade:"); + for (uint256 i = 0; i < stakingNodesManager.nodesLength(); i++) { + IStakingNode stakingNode = stakingNodesManager.nodes(i); + console.log("Node", i, ":", stakingNode.getETHBalance()); + } + + // Log pod shares for each eigenpod of each node + console.log("EigenPod shares for each StakingNode:"); + for (uint256 i = 0; i < stakingNodesManager.nodesLength(); i++) { + IStakingNode stakingNode = stakingNodesManager.nodes(i); + uint256 podShares = uint256(IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS).podOwnerShares(address(stakingNode))); + console.log("Node", i, "Shares:", podShares); + } + } +} diff --git a/test/integration/M3/Withdrawals.t.sol b/test/integration/M3/Withdrawals.t.sol new file mode 100644 index 000000000..a92a0b501 --- /dev/null +++ b/test/integration/M3/Withdrawals.t.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; + +import {IStakingNode} from "../../../src/interfaces/IStakingNode.sol"; +import {IStakingNodesManager} from "../../../src/interfaces/IStakingNodesManager.sol"; +import {IWithdrawalQueueManager} from "../../../src/interfaces/IWithdrawalQueueManager.sol"; + +import "./Base.t.sol"; + +interface IPod { + function verifyWithdrawalCredentials(uint64 beaconTimestamp, BeaconChainProofs.StateRootProof calldata stateRootProof, uint40[] calldata validatorIndices, bytes[] calldata validatorFieldsProofs, bytes32[][] calldata validatorFields) external; + function verifyCheckpointProofs(BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof, BeaconChainProofs.BalanceProof[] calldata proofs) external; +} + +interface IStakingNodeVars { + function queuedSharesAmount() external view returns (uint256); + function withdrawnETH() external view returns (uint256); +} + +contract M3WithdrawalsTest is Base { + + address public user; + + uint40 public validatorIndex; + uint256 public nodeId; + + uint256 public constant AMOUNT = 32 ether; + + // + // setup + // + + function setUp() public override { + super.setUp(); + + // deposit 32 ETH into ynETH + { + user = vm.addr(420); + vm.deal(user, AMOUNT); + vm.prank(user); + yneth.depositETH{value: AMOUNT}(user); + } + } + + // + // setup user withdrawal tests + // + + function testVerifyWithdrawalCredentials() public { + + // create staking node + { + vm.prank(actors.ops.STAKING_NODE_CREATOR); + stakingNodesManager.createStakingNode(); + nodeId = stakingNodesManager.nodesLength() - 1; + } + + // create validator + { + bytes memory _withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(nodeId); + validatorIndex = beaconChain.newValidator{ value: AMOUNT }(_withdrawalCredentials); + beaconChain.advanceEpoch_NoRewards(); + } + + // register validator + { + bytes memory _dummyPubkey = new bytes(48); + IStakingNodesManager.ValidatorData[] memory _data = new IStakingNodesManager.ValidatorData[](1); + _data[0] = IStakingNodesManager.ValidatorData({ + publicKey: _dummyPubkey, + signature: ZERO_SIGNATURE, + depositDataRoot: stakingNodesManager.generateDepositRoot( + _dummyPubkey, + ZERO_SIGNATURE, + stakingNodesManager.getWithdrawalCredentials(nodeId), + AMOUNT + ), + nodeId: nodeId + }); + vm.prank(actors.ops.VALIDATOR_MANAGER); + stakingNodesManager.registerValidators(_data); + } + + // verify withdrawal credentials + { + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndex; + CredentialProofs memory _proofs = beaconChain.getCredentialProofs(_validators); + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + IPod(address(stakingNodesManager.nodes(nodeId))).verifyWithdrawalCredentials({ + beaconTimestamp: _proofs.beaconTimestamp, + stateRootProof: _proofs.stateRootProof, + validatorIndices: _validators, + validatorFieldsProofs: _proofs.validatorFieldsProofs, + validatorFields: _proofs.validatorFields + }); + vm.stopPrank(); + + // check that unverifiedStakedETH is 0 and podOwnerShares is 32 ETH (AMOUNT) + _testVerifyWithdrawalCredentials(); + } + } + + function testVerifyCheckpoints() public { + + // setup env + { + testVerifyWithdrawalCredentials(); + beaconChain.advanceEpoch(); + } + + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(nodeId).startCheckpoint(true); + vm.stopPrank(); + + // make sure startCheckpoint cant be called again, which means that the checkpoint has started + _testStartCheckpoint(); + } + + // verify checkpoints + { + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndex; + IStakingNode _node = stakingNodesManager.nodes(nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(_validators, _node.eigenPod().currentCheckpointTimestamp()); + IPod(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); + + // check that proofsRemaining is 0 and podOwnerShares is still 32 ETH (AMOUNT) + _testVerifyCheckpointsBeforeWithdrawalRequest(); + } + } + + function testWithdrawSingleValidator() public { + testWithdraw(); + } + + function testWithdraw() internal { + + // setup env + { + testVerifyCheckpoints(); + } + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(AMOUNT); + vm.stopPrank(); + + // check that queuedSharesAmount is 32 ETH (AMOUNT) + _testQueueWithdrawals(); + } + + // create Withdrawal struct + IDelegationManager.Withdrawal[] memory _withdrawals = new IDelegationManager.Withdrawal[](1); + { + uint256[] memory _shares = new uint256[](1); + _shares[0] = AMOUNT; + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + address _stakingNode = address(stakingNodesManager.nodes(nodeId)); + _withdrawals[0] = IDelegationManager.Withdrawal({ + staker: _stakingNode, + delegatedTo: delegationManager.delegatedTo(_stakingNode), + withdrawer: _stakingNode, + nonce: delegationManager.cumulativeWithdrawalsQueued(_stakingNode) - 1, + startBlock: uint32(block.number), + strategies: _strategies, + shares: _shares + }); + } + + // exit validators + { + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + vm.roll(block.number + delegationManager.getWithdrawalDelay(_strategies)); + beaconChain.exitValidator(validatorIndex); + beaconChain.advanceEpoch_NoRewards(); + } + + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(nodeId).startCheckpoint(true); + vm.stopPrank(); + + // make sure startCheckpoint cant be called again, which means that the checkpoint has started + _testStartCheckpoint(); + } + + // verify checkpoints + { + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndex; + IStakingNode _node = stakingNodesManager.nodes(nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(_validators, _node.eigenPod().currentCheckpointTimestamp()); + IPod(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); + + // check that proofsRemaining is 0 and podOwnerShares is 0 ETH (it will actually be 1000000000 bc of EL accounting tricks) + _testVerifyCheckpointsAfterWithdrawalRequest(); + } + + // complete queued withdrawals + { + uint256[] memory _middlewareTimesIndexes = new uint256[](1); + _middlewareTimesIndexes[0] = 0; + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).completeQueuedWithdrawals(_withdrawals, _middlewareTimesIndexes); + vm.stopPrank(); + + // check that queuedSharesAmount is 0, and withdrawnETH is 32 ETH (AMOUNT), and staking pod balance is 32 ETH (AMOUNT) + _testCompleteQueuedWithdrawals(); + } + + // process principal withdrawals + uint256 _ynethBalanceBefore = address(yneth).balance; + uint256 _ynETHRedemptionAssetsBalanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + { + IStakingNodesManager.WithdrawalAction[] memory _actions = new IStakingNodesManager.WithdrawalAction[](1); + _actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: AMOUNT / 2, // 16 ETH + amountToQueue: AMOUNT / 2, // 16 ETH + rewardsAmount: 0 + }); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.processPrincipalWithdrawals({ + actions: _actions + }); + + // check that totalDepositedInPool is 16 ETH, ynETH balance is 16 ETH, and ynETHRedemptionAssetsVault balance is 16 ETH + _testProcessPrincipalWithdrawals(_ynethBalanceBefore, _ynETHRedemptionAssetsBalanceBefore); + } + } + + // + // user withdrawal tests + // + + function testRequestWithdrawal(uint256 _amount) public returns (uint256 _tokenId) { + + uint256 _userAmountBefore = yneth.balanceOf(user); + vm.assume(_amount <= _userAmountBefore / 2 && _amount > 0); // `/ 2` bc we distribute only half of what the user has deposited + + uint256 _pendingRequestedRedemptionAmountBefore = ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(); + uint256 _withdrawalQueueManagerBalanceBefore = yneth.balanceOf(address(ynETHWithdrawalQueueManager)); + vm.startPrank(user); + yneth.approve(address(ynETHWithdrawalQueueManager), _amount); + _tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(_amount); + vm.stopPrank(); + + assertEq(yneth.balanceOf(user), _userAmountBefore - _amount, "testRequestWithdrawal: E0"); + assertEq(yneth.balanceOf(address(ynETHWithdrawalQueueManager)), _withdrawalQueueManagerBalanceBefore + _amount, "testRequestWithdrawal: E1"); + + IWithdrawalQueueManager.WithdrawalRequest memory _withdrawalRequest = ynETHWithdrawalQueueManager.withdrawalRequest(_tokenId); + assertEq(_withdrawalRequest.amount, _amount, "testRequestWithdrawal: E2"); + assertEq(_withdrawalRequest.redemptionRateAtRequestTime, ynETHRedemptionAssetsVaultInstance.redemptionRate(), "testRequestWithdrawal: E3"); + assertEq(_withdrawalRequest.creationTimestamp, block.timestamp, "testRequestWithdrawal: E4"); + assertEq(_withdrawalRequest.processed, false, "testRequestWithdrawal: E5"); + assertEq(_withdrawalRequest.feeAtRequestTime, ynETHWithdrawalQueueManager.withdrawalFee(), "testRequestWithdrawal: E6"); + assertEq(ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(), _pendingRequestedRedemptionAmountBefore + ynETHWithdrawalQueueManager.calculateRedemptionAmount(_amount, ynETHRedemptionAssetsVaultInstance.redemptionRate()), "testRequestWithdrawal: E6"); + } + + function testClaimWithdrawal(uint256 _amount) public { + vm.assume(_amount > 1 ether); + + uint256 _tokenId = testRequestWithdrawal(_amount); + uint256 _userETHBalanceBefore = address(user).balance; + uint256 _expectedAmountOut = yneth.previewRedeem(_amount); + uint256 _expectedAmountOutUser = _expectedAmountOut; + uint256 _expectedAmountOutFeeReceiver; + if (ynETHWithdrawalQueueManager.withdrawalFee() > 0) { + uint256 _feeAmount = _expectedAmountOut * ynETHWithdrawalQueueManager.withdrawalFee() / ynETHWithdrawalQueueManager.FEE_PRECISION(); + _expectedAmountOutUser = _expectedAmountOut - _feeAmount; + _expectedAmountOutFeeReceiver = _feeAmount; + + } + uint256 _feeReceiverETHBalanceBefore = ynETHWithdrawalQueueManager.feeReceiver().balance; + uint256 _withdrawalQueueManagerBalanceBefore = yneth.balanceOf(address(ynETHWithdrawalQueueManager)); + + testWithdraw(); // process the withdrawal + vm.prank(actors.ops.REQUEST_FINALIZER); + uint256 finalizationId = ynETHWithdrawalQueueManager.finalizeRequestsUpToIndex(_tokenId + 1); + + + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: _tokenId, + finalizationId: finalizationId, + receiver: user + }); + vm.prank(user); + ynETHWithdrawalQueueManager.claimWithdrawals(claims); + + IWithdrawalQueueManager.WithdrawalRequest memory _withdrawalRequest = ynETHWithdrawalQueueManager.withdrawalRequest(_tokenId); + assertEq(_withdrawalRequest.processed, true, "testClaimWithdrawal: E0"); + assertEq(yneth.balanceOf(address(ynETHWithdrawalQueueManager)), _withdrawalQueueManagerBalanceBefore - _amount, "testClaimWithdrawal: E1"); + assertApproxEqAbs(address(user).balance, _userETHBalanceBefore + _expectedAmountOutUser, 10_000, "testClaimWithdrawal: E2"); + assertApproxEqAbs(ynETHWithdrawalQueueManager.feeReceiver().balance, _feeReceiverETHBalanceBefore + _expectedAmountOutFeeReceiver, 10_000, "testClaimWithdrawal: E3"); + } + + // + // internal helpers + // + + function _testVerifyWithdrawalCredentials() internal { + assertEq(stakingNodesManager.nodes(nodeId).unverifiedStakedETH(), 0, "_testVerifyWithdrawalCredentials: E0"); + assertEq(uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), AMOUNT, "_testVerifyWithdrawalCredentials: E1"); + } + + function _testStartCheckpoint() internal { + IStakingNode _node = stakingNodesManager.nodes(nodeId); + vm.expectRevert("EigenPod._startCheckpoint: must finish previous checkpoint before starting another"); + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + _node.startCheckpoint(true); + } + + function _testVerifyCheckpointsBeforeWithdrawalRequest() internal { + IEigenPod.Checkpoint memory _checkpoint = stakingNodesManager.nodes(nodeId).eigenPod().currentCheckpoint(); + assertEq(_checkpoint.proofsRemaining, 0, "_testVerifyCheckpointsBeforeWithdrawalRequest: E0"); + assertApproxEqAbs(uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), AMOUNT, 1000000000, "_testVerifyCheckpointsBeforeWithdrawalRequest: E1"); + } + + function _testQueueWithdrawals() internal { + assertEq(IStakingNodeVars(address(stakingNodesManager.nodes(nodeId))).queuedSharesAmount(), AMOUNT, "_testQueueWithdrawals: E0"); + } + + function _testVerifyCheckpointsAfterWithdrawalRequest() internal { + IEigenPod.Checkpoint memory _checkpoint = stakingNodesManager.nodes(nodeId).eigenPod().currentCheckpoint(); + assertEq(_checkpoint.proofsRemaining, 0, "_testVerifyCheckpointsAfterWithdrawalRequest: E0"); + assertEq(uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), 1000000000, "_testVerifyCheckpointsAfterWithdrawalRequest: E1"); + } + + function _testCompleteQueuedWithdrawals() internal { + assertEq(address(stakingNodesManager.nodes(nodeId)).balance, AMOUNT, "_testCompleteQueuedWithdrawals: E0"); + assertEq(IStakingNodeVars(address(stakingNodesManager.nodes(nodeId))).queuedSharesAmount(), 0, "_testCompleteQueuedWithdrawals: E1"); + assertEq(IStakingNodeVars(address(stakingNodesManager.nodes(nodeId))).withdrawnETH(), AMOUNT, "_testCompleteQueuedWithdrawals: E2"); + } + + function _testProcessPrincipalWithdrawals(uint256 _ynethBalanceBefore, uint256 _ynETHRedemptionAssetsBalanceBefore) internal { + assertEq(yneth.totalDepositedInPool(), _ynethBalanceBefore + (AMOUNT / 2), "_testProcessPrincipalWithdrawals: E0"); + assertEq(address(yneth).balance, _ynethBalanceBefore + (AMOUNT / 2), "_testProcessPrincipalWithdrawals: E1"); + assertEq(address(ynETHRedemptionAssetsVaultInstance).balance, _ynETHRedemptionAssetsBalanceBefore + AMOUNT / 2, "_testProcessPrincipalWithdrawals: E2"); + } +} \ No newline at end of file diff --git a/test/integration/M3/WithdrawalsWithRewards-Scenario.t.sol b/test/integration/M3/WithdrawalsWithRewards-Scenario.t.sol new file mode 100644 index 000000000..fa95e6910 --- /dev/null +++ b/test/integration/M3/WithdrawalsWithRewards-Scenario.t.sol @@ -0,0 +1,795 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; +import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; + +import {IStakingNode} from "../../../src/interfaces/IStakingNode.sol"; +import {IStakingNodesManager} from "../../../src/interfaces/IStakingNodesManager.sol"; +import {IWithdrawalQueueManager} from "../../../src/interfaces/IWithdrawalQueueManager.sol"; + +import "./Base.t.sol"; + +interface IPod { + function verifyWithdrawalCredentials(uint64 beaconTimestamp, BeaconChainProofs.StateRootProof calldata stateRootProof, uint40[] calldata validatorIndices, bytes[] calldata validatorFieldsProofs, bytes32[][] calldata validatorFields) external; + function verifyCheckpointProofs(BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof, BeaconChainProofs.BalanceProof[] calldata proofs) external; +} + +interface IStakingNodeVars { + function queuedSharesAmount() external view returns (uint256); + function withdrawnETH() external view returns (uint256); +} + +contract M3WithdrawalsWithRewardsTest is Base { + + address public user; + + uint40 public validatorIndex; + uint40[] validatorIndices; + uint256 public nodeId; + + uint256 public amount; + + struct TestState { + uint256 totalAssetsBefore; + uint256 totalSupplyBefore; + uint256[] stakingNodeBalancesBefore; + uint256 previousYnETHRedemptionAssetsVaultBalance; + uint256 previousYnETHBalance; + uint256 validatorCount; + } + + + function setUp() public override { + super.setUp(); + } + + function registerVerifiedValidators(uint256 totalDepositAmountInNewNode) internal returns (TestState memory state) { + + amount = totalDepositAmountInNewNode; + // deposit entire amount + { + user = vm.addr(420); + vm.deal(user, amount); + vm.prank(user); + yneth.depositETH{value: amount}(user); + + // Log ynETH balance for user + uint256 userYnETHBalance = yneth.balanceOf(user); + console.log("User ynETH balance after deposit:", userYnETHBalance); + } + + // Process rewards + rewardsDistributor.processRewards(); + + // create staking node + { + vm.prank(actors.ops.STAKING_NODE_CREATOR); + stakingNodesManager.createStakingNode(); + nodeId = stakingNodesManager.nodesLength() - 1; + } + + // Calculate validator count based on amount + uint256 validatorCount = amount / 32 ether; + + state = TestState({ + totalAssetsBefore: yneth.totalAssets(), + totalSupplyBefore: yneth.totalSupply(), + stakingNodeBalancesBefore: getAllStakingNodeBalances(), + previousYnETHRedemptionAssetsVaultBalance: ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(), + previousYnETHBalance: address(yneth).balance, + validatorCount: validatorCount + }); + + // create and register validators validator + { + // Create an array of nodeIds with length equal to validatorCount + uint256[] memory nodeIds = new uint256[](validatorCount); + for (uint256 i = 0; i < validatorCount; i++) { + nodeIds[i] = nodeId; + } + + // Call createValidators with the nodeIds array and validatorCount + validatorIndices = createValidators(nodeIds, 1); + + beaconChain.advanceEpoch_NoRewards(); + + registerValidators(nodeIds); + } + + state.stakingNodeBalancesBefore[nodeId] += validatorCount * 32 ether; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // verify withdrawal credentials + { + + CredentialProofs memory _proofs = beaconChain.getCredentialProofs(validatorIndices); + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + IPod(address(stakingNodesManager.nodes(nodeId))).verifyWithdrawalCredentials({ + beaconTimestamp: _proofs.beaconTimestamp, + stateRootProof: _proofs.stateRootProof, + validatorIndices: validatorIndices, + validatorFieldsProofs: _proofs.validatorFieldsProofs, + validatorFields: _proofs.validatorFields + }); + vm.stopPrank(); + + // check that unverifiedStakedETH is 0 and podOwnerShares is 100 ETH (amount) + // _testVerifyWithdrawalCredentials(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + } + + function startAndVerifyCheckpoint(uint256 _nodeId, TestState memory state) private { + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(_nodeId).startCheckpoint(true); + vm.stopPrank(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // verify checkpoints + { + IStakingNode _node = stakingNodesManager.nodes(_nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(validatorIndices, _node.eigenPod().currentCheckpointTimestamp()); + IPod(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); + } + } + + + struct QueuedWithdrawalInfo { + uint256 nodeId; + uint256 withdrawnAmount; + } + + function getDelegationManagerWithdrawals(QueuedWithdrawalInfo[] memory queuedWithdrawals) private view returns (IDelegationManager.Withdrawal[] memory) { + // create Withdrawal struct + IDelegationManager.Withdrawal[] memory _withdrawals = new IDelegationManager.Withdrawal[](queuedWithdrawals.length); + { + for (uint256 i = 0; i < queuedWithdrawals.length; i++) { + uint256[] memory _shares = new uint256[](1); + _shares[0] = queuedWithdrawals[i].withdrawnAmount; + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + address _stakingNode = address(stakingNodesManager.nodes(queuedWithdrawals[i].nodeId)); + _withdrawals[i] = IDelegationManager.Withdrawal({ + staker: _stakingNode, + delegatedTo: delegationManager.delegatedTo(_stakingNode), + withdrawer: _stakingNode, + nonce: delegationManager.cumulativeWithdrawalsQueued(_stakingNode) - 1, + startBlock: uint32(block.number), + strategies: _strategies, + shares: _shares + }); + } + } + + return _withdrawals; + } + + function completeQueuedWithdrawals(QueuedWithdrawalInfo[] memory queuedWithdrawals) private { + // create Withdrawal struct + IDelegationManager.Withdrawal[] memory _withdrawals = getDelegationManagerWithdrawals(queuedWithdrawals); + + { + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + + // advance time to allow completion + vm.roll(block.number + delegationManager.getWithdrawalDelay(_strategies)); + } + + // complete queued withdrawals + { + uint256[] memory _middlewareTimesIndexes = new uint256[](_withdrawals.length); + // all is zeroed out by defailt + _middlewareTimesIndexes[0] = 0; + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).completeQueuedWithdrawals(_withdrawals, _middlewareTimesIndexes); + vm.stopPrank(); + } + } + + function completeAndProcessWithdrawals( + IStakingNodesManager.WithdrawalAction memory withdrawalAction, + QueuedWithdrawalInfo[] memory queuedWithdrawals + ) public { + + + IDelegationManager.Withdrawal[] memory _withdrawals = getDelegationManagerWithdrawals(queuedWithdrawals); + + { + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + + // advance time to allow completion + vm.roll(block.number + delegationManager.getWithdrawalDelay(_strategies)); + } + + + { + uint256[] memory _middlewareTimesIndexes = new uint256[](_withdrawals.length); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + withdrawalsProcessor.completeAndProcessWithdrawalsForNode( + withdrawalAction, + _withdrawals, + _middlewareTimesIndexes + ); + } + } + + function test_userWithdrawalWithRewards_Scenario_1() public { + + // deposit 100 ETH into ynETH + TestState memory state = registerVerifiedValidators(100 ether); + + uint256 accumulatedRewards; + { + uint256 epochCount = 30; + // Advance the beacon chain by 100 epochs to simulate rewards accumulation + for (uint256 i = 0; i < epochCount; i++) { + beaconChain.advanceEpoch(); + } + accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator + } + + + // exit validators + { + for (uint256 i = 0; i < validatorIndices.length; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + beaconChain.advanceEpoch(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + // Rewards accumulated are accounted after verifying the checkpoint + state.totalAssetsBefore += accumulatedRewards; + state.stakingNodeBalancesBefore[nodeId] += accumulatedRewards; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 withdrawnAmount = 32 ether * validatorIndices.length + accumulatedRewards; + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 userWithdrawalAmount = 90 ether; + uint256 amountToReinvest = withdrawnAmount - userWithdrawalAmount - accumulatedRewards; + + { + IStakingNodesManager.WithdrawalAction[] memory _actions = new IStakingNodesManager.WithdrawalAction[](1); + _actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: amountToReinvest, + amountToQueue: userWithdrawalAmount, + rewardsAmount: accumulatedRewards + }); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.processPrincipalWithdrawals({ + actions: _actions + }); + } + + { + // Calculate fee for accumulated rewards + uint256 feesBasisPoints = rewardsDistributor.feesBasisPoints(); + uint256 _BASIS_POINTS_DENOMINATOR = 10_000; // Assuming this is the denominator used in RewardsDistributor + uint256 fees = Math.mulDiv(feesBasisPoints, accumulatedRewards, _BASIS_POINTS_DENOMINATOR); + + // Fees on accumulated rewards are substracted from the totalAssets and set to feeReceiver + state.totalAssetsBefore -= fees; + // The Balance of the stakingNode decreases by the total amount withdrawn + state.stakingNodeBalancesBefore[nodeId] -= (amountToReinvest + userWithdrawalAmount + accumulatedRewards); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Calculate user ynETH amount to redeem + uint256 userYnETHToRedeem = yneth.previewDeposit(userWithdrawalAmount); + + // Ensure user has enough ynETH balance + require(yneth.balanceOf(user) >= userYnETHToRedeem, "User doesn't have enough ynETH balance"); + + vm.startPrank(user); + yneth.approve(address(ynETHWithdrawalQueueManager), userYnETHToRedeem); + uint256 _tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(userYnETHToRedeem); + vm.stopPrank(); + + vm.prank(actors.ops.REQUEST_FINALIZER); + uint256 finalizationId = ynETHWithdrawalQueueManager.finalizeRequestsUpToIndex(_tokenId + 1); + + uint256 userBalanceBefore = user.balance; + vm.prank(user); + + ynETHWithdrawalQueueManager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + finalizationId: finalizationId, + tokenId: _tokenId, + receiver: user + }) + ); + uint256 userBalanceAfter = user.balance; + uint256 actualWithdrawnAmount = userBalanceAfter - userBalanceBefore; + // Calculate expected withdrawal amount after fee + uint256 withdrawalFee = ynETHWithdrawalQueueManager.withdrawalFee(); + uint256 expectedWithdrawnAmount = userWithdrawalAmount - (userWithdrawalAmount * withdrawalFee / ynETHWithdrawalQueueManager.FEE_PRECISION()); + + assertApproxEqAbs(actualWithdrawnAmount, expectedWithdrawnAmount, 1e9, "User did not receive expected ETH amount after fee"); + + } + + function test_userWithdrawalWithRewards_Scenario_2_queueWithdrawalsBeforeValidatorExit_with_WithdrawalsProcessor() public { + + // deposit 100 ETH into ynETH + TestState memory state = registerVerifiedValidators(100 ether); + + uint256 accumulatedRewards; + { + uint256 epochCount = 30; + // Advance the beacon chain by 100 epochs to simulate rewards accumulation + for (uint256 i = 0; i < epochCount; i++) { + beaconChain.advanceEpoch(); + } + + accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator + } + + // NOTE: Withdrawal is Queued BEFORE exiting validators. does not include rewards in this case. + + uint256 withdrawnAmount = 32 ether * validatorIndices.length; + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + // exit validators + { + for (uint256 i = 0; i < validatorIndices.length; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + beaconChain.advanceEpoch(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + // Rewards accumulated are accounted after verifying the checkpoint + state.totalAssetsBefore += accumulatedRewards; + state.stakingNodeBalancesBefore[nodeId] += accumulatedRewards; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 userWithdrawalAmount = 90 ether; + uint256 amountToReinvest = withdrawnAmount - userWithdrawalAmount - accumulatedRewards; + + { + IStakingNodesManager.WithdrawalAction memory withdrawalAction = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: amountToReinvest, + amountToQueue: userWithdrawalAmount, + rewardsAmount: accumulatedRewards + }); + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + + completeAndProcessWithdrawals(withdrawalAction, withdrawalInfos); + } + + { + // Calculate fee for accumulated rewards + uint256 feesBasisPoints = rewardsDistributor.feesBasisPoints(); + uint256 _BASIS_POINTS_DENOMINATOR = 10_000; // Assuming this is the denominator used in RewardsDistributor + uint256 fees = Math.mulDiv(feesBasisPoints, accumulatedRewards, _BASIS_POINTS_DENOMINATOR); + + // Fees on accumulated rewards are substracted from the totalAssets and set to feeReceiver + state.totalAssetsBefore -= fees; + // The Balance of the stakingNode decreases by the total amount withdrawn + state.stakingNodeBalancesBefore[nodeId] -= (amountToReinvest + userWithdrawalAmount + accumulatedRewards); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Calculate user ynETH amount to redeem + uint256 userYnETHToRedeem = yneth.previewDeposit(userWithdrawalAmount); + + // Ensure user has enough ynETH balance + require(yneth.balanceOf(user) >= userYnETHToRedeem, "User doesn't have enough ynETH balance"); + + vm.startPrank(user); + yneth.approve(address(ynETHWithdrawalQueueManager), userYnETHToRedeem); + uint256 _tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(userYnETHToRedeem); + vm.stopPrank(); + + vm.prank(actors.ops.REQUEST_FINALIZER); + uint256 finalizationId = ynETHWithdrawalQueueManager.finalizeRequestsUpToIndex(_tokenId + 1); + + uint256 userBalanceBefore = user.balance; + vm.prank(user); + + ynETHWithdrawalQueueManager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + finalizationId: finalizationId, + tokenId: _tokenId, + receiver: user + }) + ); + uint256 userBalanceAfter = user.balance; + uint256 actualWithdrawnAmount = userBalanceAfter - userBalanceBefore; + // Calculate expected withdrawal amount after fee + uint256 withdrawalFee = ynETHWithdrawalQueueManager.withdrawalFee(); + uint256 expectedWithdrawnAmount = userWithdrawalAmount - (userWithdrawalAmount * withdrawalFee / ynETHWithdrawalQueueManager.FEE_PRECISION()); + + assertApproxEqAbs(actualWithdrawnAmount, expectedWithdrawnAmount, 1e9, "User did not receive expected ETH amount after fee"); + + } + + + function test_userWithdrawalWithRewards_Scenario_2_queueWithdrawalsBeforeValidatorExit() public { + + // deposit 100 ETH into ynETH + TestState memory state = registerVerifiedValidators(100 ether); + + uint256 accumulatedRewards; + { + uint256 epochCount = 30; + // Advance the beacon chain by 100 epochs to simulate rewards accumulation + for (uint256 i = 0; i < epochCount; i++) { + beaconChain.advanceEpoch(); + } + + accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator + } + + // NOTE: Withdrawal is Queued BEFORE exiting validators. does not include rewards in this case. + + uint256 withdrawnAmount = 32 ether * validatorIndices.length; + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + // exit validators + { + for (uint256 i = 0; i < validatorIndices.length; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + beaconChain.advanceEpoch(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + // Rewards accumulated are accounted after verifying the checkpoint + state.totalAssetsBefore += accumulatedRewards; + state.stakingNodeBalancesBefore[nodeId] += accumulatedRewards; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 userWithdrawalAmount = 90 ether; + uint256 amountToReinvest = withdrawnAmount - userWithdrawalAmount - accumulatedRewards; + + { + IStakingNodesManager.WithdrawalAction[] memory _actions = new IStakingNodesManager.WithdrawalAction[](1); + _actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: amountToReinvest, + amountToQueue: userWithdrawalAmount, + rewardsAmount: accumulatedRewards + }); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.processPrincipalWithdrawals({ + actions: _actions + }); + } + + { + // Calculate fee for accumulated rewards + uint256 feesBasisPoints = rewardsDistributor.feesBasisPoints(); + uint256 _BASIS_POINTS_DENOMINATOR = 10_000; // Assuming this is the denominator used in RewardsDistributor + uint256 fees = Math.mulDiv(feesBasisPoints, accumulatedRewards, _BASIS_POINTS_DENOMINATOR); + + // Fees on accumulated rewards are substracted from the totalAssets and set to feeReceiver + state.totalAssetsBefore -= fees; + // The Balance of the stakingNode decreases by the total amount withdrawn + state.stakingNodeBalancesBefore[nodeId] -= (amountToReinvest + userWithdrawalAmount + accumulatedRewards); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Calculate user ynETH amount to redeem + uint256 userYnETHToRedeem = yneth.previewDeposit(userWithdrawalAmount); + + // Ensure user has enough ynETH balance + require(yneth.balanceOf(user) >= userYnETHToRedeem, "User doesn't have enough ynETH balance"); + + vm.startPrank(user); + yneth.approve(address(ynETHWithdrawalQueueManager), userYnETHToRedeem); + uint256 _tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(userYnETHToRedeem); + vm.stopPrank(); + + vm.prank(actors.ops.REQUEST_FINALIZER); + uint256 finalizationId = ynETHWithdrawalQueueManager.finalizeRequestsUpToIndex(_tokenId + 1); + + uint256 userBalanceBefore = user.balance; + vm.prank(user); + + ynETHWithdrawalQueueManager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + finalizationId: finalizationId, + tokenId: _tokenId, + receiver: user + }) + ); + uint256 userBalanceAfter = user.balance; + uint256 actualWithdrawnAmount = userBalanceAfter - userBalanceBefore; + // Calculate expected withdrawal amount after fee + uint256 withdrawalFee = ynETHWithdrawalQueueManager.withdrawalFee(); + uint256 expectedWithdrawnAmount = userWithdrawalAmount - (userWithdrawalAmount * withdrawalFee / ynETHWithdrawalQueueManager.FEE_PRECISION()); + + assertApproxEqAbs(actualWithdrawnAmount, expectedWithdrawnAmount, 1e9, "User did not receive expected ETH amount after fee"); + + } + + function test_userWithdrawalWithRewards_Scenario_3_withdrawEverything() public { + + // exactly 2 validators + TestState memory state = registerVerifiedValidators(64 ether); + + uint256 accumulatedRewards; + { + uint256 epochCount = 30; + // Advance the beacon chain by 100 epochs to simulate rewards accumulation + for (uint256 i = 0; i < epochCount; i++) { + beaconChain.advanceEpoch(); + } + + accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator + } + + // NOTE: Withdrawal is Queued BEFORE exiting validators. does not include rewards in this case. + + uint256 withdrawnAmount = amount; + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + // exit validators + { + for (uint256 i = 0; i < validatorIndices.length; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + beaconChain.advanceEpoch(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + // Rewards accumulated are accounted after verifying the checkpoint + state.totalAssetsBefore += accumulatedRewards; + state.stakingNodeBalancesBefore[nodeId] += accumulatedRewards; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Assert that the node's podOwnerShares is accumulatedRewards after completing withdrawals + assertEq( + eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId))), + int256(accumulatedRewards), + "Node's podOwnerShares should be 0 after completing withdrawals" + ); + } + + function test_userWithdrawalWithRewards_Scenario_4_slashAllValidatorsAndWithdrawEverything() public { + + // exactly 2 validators + TestState memory state = registerVerifiedValidators(64 ether); + + uint256 accumulatedRewards; + { + uint256 epochCount = 30; + // Advance the beacon chain by 100 epochs to simulate rewards accumulation + for (uint256 i = 0; i < epochCount; i++) { + beaconChain.advanceEpoch(); + } + + accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator + } + + uint256 withdrawnAmount = amount; + + // NOTE: This triggers the a exit of all validators + beaconChain.slashValidators(validatorIndices); + beaconChain.advanceEpoch(); + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + // Print withdrawableRestakedExecutionLayerGwei for each EigenPod + IEigenPod pod = stakingNodesManager.nodes(nodeId).eigenPod(); + uint64 withdrawableGwei = pod.withdrawableRestakedExecutionLayerGwei(); + console.log("Withdrawable GWEI for EigenPod:", withdrawableGwei); + + uint256 totalSlashAmount = beaconChain.SLASH_AMOUNT_GWEI() * state.validatorCount * 1e9; + state.totalAssetsBefore = state.totalAssetsBefore + accumulatedRewards - totalSlashAmount; + state.stakingNodeBalancesBefore[nodeId] = state.stakingNodeBalancesBefore[nodeId] + accumulatedRewards - totalSlashAmount; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + } + + function test_userWithdrawalWithRewards_Scenario_4_slashAllValidatorsWithNoRewardsAndWithdrawEverything() public { + + // exactly 2 validators + TestState memory state = registerVerifiedValidators(64 ether); + + uint256 withdrawnAmount = amount; + + // NOTE: This triggers the a exit of all validators + beaconChain.slashValidators(validatorIndices); + beaconChain.advanceEpoch(); + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + startAndVerifyCheckpoint(nodeId, state); + + uint256 totalSlashAmount = beaconChain.SLASH_AMOUNT_GWEI() * state.validatorCount * 1e9; + state.totalAssetsBefore = state.totalAssetsBefore - totalSlashAmount; + state.stakingNodeBalancesBefore[nodeId] = state.stakingNodeBalancesBefore[nodeId] - totalSlashAmount; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Assert that the node's queuedSharesAmount is 0 after completing withdrawals + assertEq( + IStakingNodeVars(address(stakingNodesManager.nodes(nodeId))).queuedSharesAmount(), + 0, + "Node's queuedSharesAmount should be 0 after completing withdrawals" + ); + // Assert that the node's podOwnerShares is 0 + assertEq( + eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId))), + int256(0), + "Node's podOwnerShares should be 0 after completing withdrawals" + ); + // Assert that the node's withdrawnETH is equal to withdrawnAmount minus totalSlashAmount + uint256 expectedWithdrawnETH = withdrawnAmount - totalSlashAmount; + assertEq( + IStakingNodeVars(address(stakingNodesManager.nodes(nodeId))).withdrawnETH(), + expectedWithdrawnETH, + "Node's withdrawnETH should be equal to withdrawnAmount minus totalSlashAmount" + ); + } + + function test_userWithdrawalWithRewards_Scenario_5_slashAllValidatorsWithNoRewardsAndWithdrawEverything() public { + + // exactly 2 validators + TestState memory state = registerVerifiedValidators(64 ether); + + uint256 withdrawnAmount = amount; + + // Validators are not slashed and no rewards are earned before queueing withdrawals. + + // queue withdrawals + { + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount); + vm.stopPrank(); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // Trigger slashing of validators after withdrawals were queued + // NOTE: This triggers the a exit of all validators + beaconChain.slashValidators(validatorIndices); + beaconChain.advanceEpoch(); + + startAndVerifyCheckpoint(nodeId, state); + + uint256 totalSlashAmount = beaconChain.SLASH_AMOUNT_GWEI() * state.validatorCount * 1e9; + state.totalAssetsBefore = state.totalAssetsBefore - totalSlashAmount; + state.stakingNodeBalancesBefore[nodeId] = state.stakingNodeBalancesBefore[nodeId] - totalSlashAmount; + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // check podOwnerShares are now negative becaose validators were slashed after withdrawals were queued up + assertEq( + eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId))), + 0 - int256(totalSlashAmount), + "Node's podOwnerShares should be 0 after completing withdrawals" + ); + + QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1); + withdrawalInfos[0] = QueuedWithdrawalInfo({ + nodeId: nodeId, + withdrawnAmount: withdrawnAmount + }); + completeQueuedWithdrawals(withdrawalInfos); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + } +} \ No newline at end of file diff --git a/test/integration/PooledDepositsVault.t.sol b/test/integration/PooledDepositsVault.t.sol deleted file mode 100644 index 8cb675805..000000000 --- a/test/integration/PooledDepositsVault.t.sol +++ /dev/null @@ -1,322 +0,0 @@ -pragma solidity ^0.8.24; - -import "forge-std/Test.sol"; -import {PooledDepositsVault} from "src/PooledDepositsVault.sol"; -import {IynETH} from "src/interfaces/IynETH.sol"; -import "test/integration/IntegrationBaseTest.sol"; -import "src/ynBase.sol"; - - -contract PooledDepositsVaultTest is IntegrationBaseTest { - - function createPooledDeposits() internal returns (PooledDepositsVault pooledDepositsVault, address owner) { - PooledDepositsVault implementation = new PooledDepositsVault(); - bytes memory initData = abi.encodeWithSelector(PooledDepositsVault.initialize.selector, address(this)); - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(implementation), address(this), initData); - pooledDepositsVault = PooledDepositsVault(payable(address(proxy))); - owner = address(this); - return (pooledDepositsVault, owner); - } - - function testDepositFuzz(uint256 depositAmount) public { - // Fuzz input - vm.assume(depositAmount > 0.01 ether && depositAmount <= 100 ether); // Assuming a reasonable range for deposit amounts - // Arrange - (PooledDepositsVault pooledDepositsVault, ) = createPooledDeposits(); - address depositor = address(this); - - // Act - vm.deal(depositor, depositAmount); - vm.startPrank(depositor); - pooledDepositsVault.deposit{value: depositAmount}(); - vm.stopPrank(); - - // Assert - assertEq(pooledDepositsVault.balances(depositor), depositAmount, "Deposit amount should be recorded in the depositor's balance"); - } - function testMultipleSequentialDepositsForSameUserFuzz(uint256 depositAmount1) public { - // Fuzz inputs - vm.assume(depositAmount1 > 0.01 ether && depositAmount1 <= 100 ether); // Assuming a reasonable range for the first deposit amount - uint256 depositAmount2 = depositAmount1 + 100 ether; - - // Arrange - (PooledDepositsVault pooledDepositsVault, ) = createPooledDeposits(); - address depositor = address(this); - uint256 expectedBalanceAfterFirstDeposit = depositAmount1; - uint256 expectedBalanceAfterSecondDeposit = depositAmount1 + depositAmount2; - - // Act and Assert - vm.deal(depositor, expectedBalanceAfterSecondDeposit); - vm.startPrank(depositor); - pooledDepositsVault.deposit{value: depositAmount1}(); - assertEq(pooledDepositsVault.balances(depositor), expectedBalanceAfterFirstDeposit, "Balance after first deposit incorrect"); - pooledDepositsVault.deposit{value: depositAmount2}(); - assertEq(pooledDepositsVault.balances(depositor), expectedBalanceAfterSecondDeposit, "Balance after second deposit incorrect"); - vm.stopPrank(); - } - - function testFinalizeDeposits() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - address[] memory depositors = new address[](1); - depositors[0] = address(this); - uint256 depositAmount = 1 ether; - vm.deal(address(this), depositAmount); - pooledDepositsVault.deposit{value: depositAmount}(); - - // Set ynETH before finalizing deposits - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - - // Act - vm.warp(block.timestamp + 3 days); // Move time forward to allow finalizing deposits - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert - // Assuming ynETH's depositETH function simply mints 1:1 ynETH for ETH deposited - uint256 expectedYnETHAmount = depositAmount; - assertEq(yneth.balanceOf(address(this)), expectedYnETHAmount, "ynETH should be minted and sent to the depositor"); - } - - function testFinalizeDepositsFuzz(uint8 depositorsCount, uint256 baseDepositAmount) public { - // Fuzz inputs - vm.assume(depositorsCount > 0 && depositorsCount <= 100); // Limiting the number of depositors to a reasonable range - vm.assume(baseDepositAmount > 0.01 ether && baseDepositAmount <= 100 ether); // Assuming a reasonable range for deposit amounts - - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - address[] memory depositors = new address[](depositorsCount); - uint256 totalDepositAmount = 0; - uint256 varyingDepositAmount = baseDepositAmount; - - for (uint8 i = 0; i < depositorsCount; i++) { - address depositor = address(uint160(uint(keccak256(abi.encodePacked(i, block.timestamp))))); - depositors[i] = depositor; - vm.deal(depositor, varyingDepositAmount); - vm.prank(depositor); - pooledDepositsVault.deposit{value: varyingDepositAmount}(); - totalDepositAmount += varyingDepositAmount; - varyingDepositAmount += 1 ether; // Increase the deposit amount by 1 ether for each depositor - } - - // Set ynETH before finalizing deposits - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - - // Act - vm.warp(block.timestamp + 3 days); // Move time forward to allow finalizing deposits - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert - // Assuming ynETH's depositETH function simply mints 1:1 ynETH for ETH deposited - varyingDepositAmount = baseDepositAmount; // Reset varyingDepositAmount for assertion checks - for (uint8 i = 0; i < depositorsCount; i++) { - uint256 expectedYnETHAmount = varyingDepositAmount; - assertEq(yneth.balanceOf(depositors[i]), expectedYnETHAmount, "ynETH should be minted and sent to the depositor"); - varyingDepositAmount += 1 ether; // Increase the expected amount by 1 ether for each depositor - } - } - - function testDepositAfterSettingYnETH() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - uint256 depositAmount = 1 ether; - address depositor = address(0x123); // Use a test address instead of address(this) - vm.deal(depositor, depositAmount); - - // Set ynETH before finalizing deposits - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - - // Act & Assert - vm.expectRevert(PooledDepositsVault.YnETHIsSet.selector); - vm.prank(depositor); - pooledDepositsVault.deposit{value: depositAmount}(); - } - - function testFinalizeDepositsBeforeSettingYnETH() public { - // Arrange - (PooledDepositsVault pooledDepositsVault,) = createPooledDeposits(); - address[] memory depositors = new address[](1); - depositors[0] = address(0x123); // Use a test address instead of address(this) - - // Act & Assert - vm.expectRevert(PooledDepositsVault.YnETHNotSet.selector); - pooledDepositsVault.finalizeDeposits(depositors); - } - - function testDepositZeroAmount() public { - // Arrange - (PooledDepositsVault pooledDepositsVault,) = createPooledDeposits(); - - // Act & Assert - vm.expectRevert(PooledDepositsVault.DepositMustBeGreaterThanZero.selector); - pooledDepositsVault.deposit{value: 0}(); - } - - function testFinalizeDepositsWithNoDepositors() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - address[] memory depositors = new address[](0); - - // Set ynETH before finalizing deposits - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert - // No revert means success, but nothing to assert as there were no depositors - } - - function testDirectETHSendForDepositFuzzed(uint256 depositAmount) public { - // Only proceed with valid deposit amounts to avoid unnecessary test failures - vm.assume(depositAmount > 0 && depositAmount <= 100 ether); - - // Arrange - (PooledDepositsVault pooledDepositsVault,) = createPooledDeposits(); - address depositor = address(0x123); - vm.deal(depositor, depositAmount); - vm.prank(depositor); - - // Act - (bool success, ) = address(pooledDepositsVault).call{value: depositAmount}(""); - - // Assert - assertTrue(success, "ETH send should succeed"); - assertEq(pooledDepositsVault.balances(depositor), depositAmount, "Deposit amount should be recorded in balances"); - } - - function testFinalizeDepositsMultipleTimesForSameUser() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - address[] memory depositors = new address[](1); - address depositor = address(0x1234); - depositors[0] = depositor; - uint256 depositAmount = 1 ether; - - vm.deal(depositor, depositAmount + 100 ether); - vm.prank(depositor); - pooledDepositsVault.deposit{value: depositAmount}(); - - // Set ynETH before finalizing deposits - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - - // Act - vm.warp(block.timestamp + 1 days + 1); // Move time forward to allow finalizing deposits - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert first finalize - uint256 sharesAfterFirstFinalize = IynETH(address(yneth)).balanceOf(depositor); - assertTrue(sharesAfterFirstFinalize > 0, "Shares should be allocated after first finalize"); - - // Check balance after first finalize - uint256 balanceAfterFirstFinalize = pooledDepositsVault.balances(depositor); - assertEq(balanceAfterFirstFinalize, 0, "Balance should be 0 after first finalize"); - - // Attempt to finalize again - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert second finalize - uint256 sharesAfterSecondFinalize = IynETH(address(yneth)).balanceOf(depositor); - assertEq(sharesAfterFirstFinalize, sharesAfterSecondFinalize, "Shares should not change after second finalize"); - - // Check balance after second finalize - uint256 balanceAfterSecondFinalize = pooledDepositsVault.balances(depositor); - assertEq(balanceAfterSecondFinalize, 0, "Balance should remain 0 after second finalize"); - } - - function testDepositsReEnabledOnYnETHReset() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - uint256 depositAmount = 1 ether; - vm.deal(address(this), depositAmount); - - // Set ynETH to a non-zero address and then reset it to zero - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(1))); // Set to a mock address - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(0))); // Reset to zero - - // Act - (bool success, ) = address(pooledDepositsVault).call{value: depositAmount}(""); - - // Assert - assertTrue(success, "Deposit should succeed after ynETH is reset"); - assertEq(pooledDepositsVault.balances(address(this)), depositAmount, "Deposit amount should be recorded in balances after ynETH reset"); - } - - function testDepositFinalizeResetAndDepositFinalizeFlow() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - uint256 firstDepositAmount = 1 ether; - uint256 secondDepositAmount = 2 ether; - address[] memory depositors = new address[](1); - address depositor = address(0xBEEF); - depositors[0] = depositor; - vm.deal(depositor, firstDepositAmount + secondDepositAmount + 100 ether); - - // Act 1 - Deposit and finalize - vm.prank(depositor); - pooledDepositsVault.deposit{value: firstDepositAmount}(); - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert 1 - uint256 sharesAfterFirstFinalize = IynETH(address(yneth)).balanceOf(depositor); - assertEq(sharesAfterFirstFinalize, firstDepositAmount, "Shares should equal the deposit amount after first finalize"); - - // Reset ynETH to 0 - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(0))); - - // Act 2 - Deposit and finalize after resetting ynETH - vm.prank(depositor); - pooledDepositsVault.deposit{value: secondDepositAmount}(); - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); // Set ynETH back to a valid address - pooledDepositsVault.finalizeDeposits(depositors); - - // Assert 2 - uint256 sharesAfterSecondFinalize = IynETH(address(yneth)).balanceOf(depositor); - assertEq(sharesAfterSecondFinalize, sharesAfterFirstFinalize + secondDepositAmount, "Shares should exactly match the total deposits after second finalize"); - } - - function testDepositFinalizeTransferAndUnpauseFlow() public { - // Arrange - (PooledDepositsVault pooledDepositsVault, address owner) = createPooledDeposits(); - uint256 depositAmount = 1 ether; - address recipient = address(0x123); - address depositor = address(0xBEEF); - vm.deal(depositor, depositAmount); - - // Act 1 - Deposit and finalize - vm.prank(depositor); - pooledDepositsVault.deposit{value: depositAmount}(); - vm.prank(owner); - pooledDepositsVault.setYnETH(IynETH(address(yneth))); - address[] memory depositors = new address[](1); - depositors[0] = depositor; - pooledDepositsVault.finalizeDeposits(depositors); - - // Try to transfer ynETH tokens and expect revert due to paused transfers - vm.expectRevert(ynBase.TransfersPaused.selector); - IynETH(address(yneth)).transfer(recipient, depositAmount); - - // Unpause transfers - vm.prank(actors.admin.UNPAUSE_ADMIN); - yneth.unpauseTransfers(); - - // Act 2 - Transfer ynETH tokens after unpausing - vm.prank(depositor); - IynETH(address(yneth)).transfer(recipient, depositAmount); - - // Assert - assertEq(IynETH(address(yneth)).balanceOf(recipient), depositAmount, "Recipient should receive the correct amount of ynETH tokens"); - } - - receive() external payable {} -} - diff --git a/test/integration/ReferralDepositAdapter.t.sol b/test/integration/ReferralDepositAdapter.t.sol index 537df8fb2..2c2ef9c83 100644 --- a/test/integration/ReferralDepositAdapter.t.sol +++ b/test/integration/ReferralDepositAdapter.t.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: BSD 3-Clause License pragma solidity ^0.8.24; diff --git a/test/integration/StakingNode.t.sol b/test/integration/StakingNode.t.sol index 99fbbee3c..8a7394425 100644 --- a/test/integration/StakingNode.t.sol +++ b/test/integration/StakingNode.t.sol @@ -6,400 +6,106 @@ import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contrac import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol"; import {IPausable} from "lib/eigenlayer-contracts/src/contracts/interfaces/IPausable.sol"; import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; -import {IBeaconChainOracle} from "lib/eigenlayer-contracts/src/contracts/interfaces/IBeaconChainOracle.sol"; -import {IntegrationBaseTest} from "test/integration/IntegrationBaseTest.sol"; import {IStakingNode} from "src/interfaces/IStakingNode.sol"; import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; -import {BeaconChainProofs} from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {StakingNode} from "src/StakingNode.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; -import {ProofUtils} from "test/utils/ProofUtils.sol"; import {ISignatureUtils} from "lib/eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; -import { MockEigenLayerBeaconOracle } from "../mocks/MockEigenLayerBeaconOracle.sol"; import {BytesLib} from "lib/eigenlayer-contracts/src/contracts/libraries/BytesLib.sol"; import { EigenPod } from "lib/eigenlayer-contracts/src/contracts/pods/EigenPod.sol"; -import {MockEigenPod} from "../mocks/MockEigenPod.sol"; -import { MockEigenPodManager } from "../mocks/MockEigenPodManager.sol"; -import { MockStakingNode } from "../mocks/MockStakingNode.sol"; import { EigenPodManager } from "lib/eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol"; import {IETHPOSDeposit} from "lib/eigenlayer-contracts/src/contracts/interfaces/IETHPOSDeposit.sol"; import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import { TransparentUpgradeableProxy } from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - +import {BeaconChainMock, BeaconChainProofs, CheckpointProofs, CredentialProofs } from "lib/eigenlayer-contracts/src/test/integration/mocks/BeaconChainMock.t.sol"; import { ProofParsingV1 } from "test/eigenlayer-utils/ProofParsingV1.sol"; import {Utils} from "script/Utils.sol"; +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; +import {StakingNodeTestBase, IEigenPodSimplified } from "./StakingNodeTestBase.sol"; -interface ITransparentUpgradeableProxy { - function upgradeTo(address) external payable; -} - -contract StakingNodeTestBase is IntegrationBaseTest, ProofParsingV1 { - - string DEFAULT_PROOFS_PATH = "lib/eigenlayer-contracts/src/test/test-data/fullWithdrawalProof_Latest.json"; - struct ValidatorProofs { - BeaconChainProofs.StateRootProof stateRootProof; - uint40[] validatorIndices; - bytes[] withdrawalCredentialProofs; - bytes[] validatorFieldsProofs; - bytes32[][] validatorFields; - } - - function setupStakingNode(uint256 depositAmount) public returns (IStakingNode, IEigenPod) { - - address addr1 = vm.addr(100); - - require(depositAmount % 32 ether == 0, "depositAmount must be a multiple of 32 ether"); - - uint256 validatorCount = depositAmount / 32 ether; - - vm.deal(addr1, depositAmount); - - vm.prank(addr1); - yneth.depositETH{value: depositAmount}(addr1); - - vm.prank(actors.ops.STAKING_NODE_CREATOR); - IStakingNode stakingNodeInstance = stakingNodesManager.createStakingNode(); +contract StakingNodeEigenPod is StakingNodeTestBase { - uint256 nodeId = 0; + // FIXME: update or delete to accomdate for M3 + function testCreateNodeAndVerifyPodStateIsValid() public { - IStakingNodesManager.ValidatorData[] memory validatorData = new IStakingNodesManager.ValidatorData[](validatorCount); - for (uint256 i = 0; i < validatorCount; i++) { - bytes memory publicKey = abi.encodePacked(uint256(i)); - publicKey = bytes.concat(publicKey, new bytes(ZERO_PUBLIC_KEY.length - publicKey.length)); - validatorData[i] = IStakingNodesManager.ValidatorData({ - publicKey: publicKey, - signature: ZERO_SIGNATURE, - nodeId: nodeId, - depositDataRoot: bytes32(0) - }); - } + uint depositAmount = 32 ether; - bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(nodeId); + address user = vm.addr(156737); - for (uint256 i = 0; i < validatorData.length; i++) { - uint256 amount = depositAmount / validatorData.length; - bytes32 depositDataRoot = stakingNodesManager.generateDepositRoot(validatorData[i].publicKey, validatorData[i].signature, withdrawalCredentials, amount); - validatorData[i].depositDataRoot = depositDataRoot; - } - - vm.prank(actors.ops.VALIDATOR_MANAGER); - stakingNodesManager.registerValidators(validatorData); + // Create a user address and fund it with 1000 ETH + vm.deal(user, 1000 ether); - uint256 actualETHBalance = stakingNodeInstance.getETHBalance(); - assertEq(actualETHBalance, depositAmount, "ETH balance does not match expected value"); + yneth.depositETH{value: depositAmount }(user); + uint256[] memory nodeIds = createStakingNodes(1); + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeIds[0]); IEigenPod eigenPodInstance = stakingNodeInstance.eigenPod(); - return (stakingNodeInstance, eigenPodInstance); - } - - function _getLatestBlockRoot() public returns (bytes32) { - return getLatestBlockRoot(); - } - - function getWithdrawalCredentialParams() public returns (ValidatorProofs memory) { - ValidatorProofs memory validatorProofs; - - validatorProofs.validatorIndices = new uint40[](1); - validatorProofs.withdrawalCredentialProofs = new bytes[](1); - validatorProofs.validatorFieldsProofs = new bytes[](1); - validatorProofs.validatorFields = new bytes32[][](1); - - // Set beacon state root, validatorIndex - validatorProofs.stateRootProof.beaconStateRoot = getBeaconStateRoot(); - validatorProofs.stateRootProof.proof = getStateRootProof(); - validatorProofs.validatorIndices[0] = uint40(getValidatorIndex()); - validatorProofs.withdrawalCredentialProofs[0] = abi.encodePacked(getWithdrawalCredentialProof()); // Validator fields are proven here - // validatorProofs.validatorFieldsProofs[0] = getWithdrawalCredentialProof(); - validatorProofs.validatorFields[0] = getValidatorFields(); - - return validatorProofs; - } - - function bytes32ToData(bytes32 data) public pure returns (address) { - return address(uint160(uint256(data))); - } - - function getWithdrawalAddress() public returns (address) { - bytes32[] memory validatorFields = getValidatorFields(); - return bytes32ToData(validatorFields[1]); - } -} - - -contract StakingNodeEigenPod is StakingNodeTestBase { - - function testCreateNodeAndVerifyPodStateIsValid() public { - - uint depositAmount = 32 ether; - - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(depositAmount); - - // Collapsed variable declarations into direct usage within assertions and conditions // TODO: double check this is the desired state for a pod. // we can't delegate on mainnet at this time so one should be able to farm points without delegating assertEq(eigenPodInstance.withdrawableRestakedExecutionLayerGwei(), 0, "Restaked Gwei should be 0"); assertEq(address(eigenPodManager), address(eigenPodInstance.eigenPodManager()), "EigenPodManager should match"); assertEq(eigenPodInstance.podOwner(), address(stakingNodeInstance), "Pod owner address does not match"); - assertEq(eigenPodInstance.mostRecentWithdrawalTimestamp(), 0, "Most recent withdrawal block should be greater than 0"); address payable eigenPodAddress = payable(address(eigenPodInstance)); - // simulate ETH entering the pod by direct transfer as non-beacon chain ETH - uint256 rewardsSweeped = 1 ether; - vm.deal(address(this), rewardsSweeped); - (bool success,) = eigenPodAddress.call{value: rewardsSweeped}(""); - require(success, "Failed to send rewards to EigenPod"); - - // trigger non beacon chain ETH withdrawal - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.withdrawNonBeaconChainETHBalanceWei(); - vm.stopPrank(); - - IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter(); - uint256 withdrawalDelayBlocks = delayedWithdrawalRouter.withdrawalDelayBlocks(); - vm.roll(block.number + withdrawalDelayBlocks + 1); - - delayedWithdrawalRouter.claimDelayedWithdrawals(address(stakingNodeInstance), type(uint256).max); - - uint256 balanceBeforeClaim = address(consensusLayerReceiver).balance; - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.processDelayedWithdrawals(); - uint256 balanceAfterClaim = address(consensusLayerReceiver).balance; - uint256 rewardsAmount = balanceAfterClaim - balanceBeforeClaim; - - assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value"); - - rewardsDistributor.processRewards(); - - uint256 fee = uint256(rewardsDistributor.feesBasisPoints()); - uint256 finalRewardsReceived = rewardsAmount - (rewardsAmount * fee / 10000); - - // Assert total assets after claiming delayed withdrawals - uint256 totalAssets = yneth.totalAssets(); - assertEq(totalAssets, finalRewardsReceived + depositAmount, "Total assets after claiming delayed withdrawals do not match expected value"); - } -} - - -contract StakingNodeWithdrawNonBeaconChainETHBalanceWei is StakingNodeTestBase { - using stdStorage for StdStorage; - - function testWithdrawNonBeaconChainETHBalanceWeiAndProcessNonBeaconChainETHWithdrawals() public { + // Get initial pod owner shares + int256 initialPodOwnerShares = eigenPodManager.podOwnerShares(address(stakingNodeInstance)); + // Assert that initial pod owner shares are 0 + assertEq(initialPodOwnerShares, 0, "Initial pod owner shares should be 0"); - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(32 ether); - - address payable eigenPodAddress = payable(address(eigenPodInstance)); // simulate ETH entering the pod by direct transfer as non-beacon chain ETH uint256 rewardsSweeped = 1 ether; vm.deal(address(this), rewardsSweeped); (bool success,) = eigenPodAddress.call{value: rewardsSweeped}(""); require(success, "Failed to send rewards to EigenPod"); - // trigger withdrawNonBeaconChainETHBalanceWei succesfully - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.withdrawNonBeaconChainETHBalanceWei(); - vm.stopPrank(); - - IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter(); - uint256 withdrawalDelayBlocks = delayedWithdrawalRouter.withdrawalDelayBlocks(); - vm.roll(block.number + withdrawalDelayBlocks + 1); - - delayedWithdrawalRouter.claimDelayedWithdrawals(address(stakingNodeInstance), type(uint256).max); - - uint256 balanceBeforeClaim = address(consensusLayerReceiver).balance; - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.processDelayedWithdrawals(); - uint256 balanceAfterClaim = address(consensusLayerReceiver).balance; - uint256 rewardsAmount = balanceAfterClaim - balanceBeforeClaim; - - assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value"); - } - - function testWithdrawNonBeaconChainETHBalanceWeiAndProcessNonBeaconChainETHWithdrawalsForALargeAmount() public { - - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(32 ether); - - address payable eigenPodAddress = payable(address(eigenPodInstance)); - - // a large amount of ETH from an arbitrary source is sent to the EigenPod - uint256 rewardsSweeped = 1000 ether; - vm.deal(address(this), rewardsSweeped); - (bool success,) = eigenPodAddress.call{value: rewardsSweeped}(""); - require(success, "Failed to send rewards to EigenPod"); - - // trigger withdraw before restaking succesfully - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.withdrawNonBeaconChainETHBalanceWei(); - vm.stopPrank(); - - IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter(); - uint256 withdrawalDelayBlocks = delayedWithdrawalRouter.withdrawalDelayBlocks(); - vm.roll(block.number + withdrawalDelayBlocks + 1); - - delayedWithdrawalRouter.claimDelayedWithdrawals(address(stakingNodeInstance), type(uint256).max); - - uint256 balanceBeforeClaim = address(consensusLayerReceiver).balance; - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.processDelayedWithdrawals(); - uint256 balanceAfterClaim = address(consensusLayerReceiver).balance; - uint256 rewardsAmount = balanceAfterClaim - balanceBeforeClaim; - - assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value"); - } - - function testProcessNonBeaconChainETHWithdrawalsWithExistingValidatorPrincipal() public { - - uint256 activeValidators = 5; - - uint256 depositAmount = activeValidators * 32 ether; - - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(depositAmount); - - address payable eigenPodAddress = payable(address(eigenPodInstance)); - // Arbitrary rewards sent to the Eigenpod - uint256 rewardsSweeped = 100 ether; - vm.deal(address(this), rewardsSweeped); - (bool success,) = eigenPodAddress.call{value: rewardsSweeped}(""); - require(success, "Failed to send rewards to EigenPod"); - - // trigger withdraw before restaking succesfully - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.withdrawNonBeaconChainETHBalanceWei(); - vm.stopPrank(); - - IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter(); - vm.roll(block.number + delayedWithdrawalRouter.withdrawalDelayBlocks() + 1); - - delayedWithdrawalRouter.claimDelayedWithdrawals(address(stakingNodeInstance), type(uint256).max); - - uint256 balanceBeforeClaim = address(consensusLayerReceiver).balance; - - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.processDelayedWithdrawals(); - uint256 balanceAfterClaim = address(consensusLayerReceiver).balance; - uint256 rewardsAmount = balanceAfterClaim - balanceBeforeClaim; - - assertEq(stakingNodeInstance.getETHBalance(), depositAmount, "StakingNode ETH balance does not match expected value"); - assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value"); + // Assert that pod owner shares remain the same + assertEq(initialPodOwnerShares, 0, "Pod owner shares should not change"); } + + function testCreateNodeVerifyPodStateAndCheckpoint() public { + uint256 depositAmount = 32 ether; + address user = vm.addr(156737); - function testProcessNonBeaconChainETHWithdrawalsWhenETHArrivesFromBeaconChainAsWell() public { - - uint256 activeValidators = 5; - - uint256 depositAmount = activeValidators * 32 ether; + // Fund user and deposit ETH + vm.deal(user, 1000 ether); + yneth.depositETH{value: depositAmount}(user); - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(depositAmount); + // Create staking node and get instances + uint256[] memory nodeIds = createStakingNodes(1); + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeIds[0]); + IEigenPod eigenPodInstance = stakingNodeInstance.eigenPod(); - address payable eigenPodAddress = payable(address(eigenPodInstance)); - // Arbitrary rewards sent to the Eigenpod - uint256 rewardsSweeped = 100 ether; + // Simulate ETH entering the pod + uint256 rewardsSweeped = 1 ether; vm.deal(address(this), rewardsSweeped); - (bool success,) = eigenPodAddress.call{value: rewardsSweeped}(""); + (bool success,) = payable(address(eigenPodInstance)).call{value: rewardsSweeped}(""); require(success, "Failed to send rewards to EigenPod"); - uint256 withdrawnValidators = 1; - uint256 withdrawnPrincipal = withdrawnValidators * 32 ether; - - // this increases the balance of the EigenPod without triggering - // receive or fallback just like beacon chain ETH rewards or withdrawals would - vm.deal(eigenPodAddress, eigenPodAddress.balance + withdrawnPrincipal); - uint256 expectedBalance = rewardsSweeped + withdrawnPrincipal; - assertEq(address(eigenPodInstance).balance, expectedBalance, "EigenPod balance does not match expected value"); - - // trigger withdraw before restaking succesfully - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.withdrawNonBeaconChainETHBalanceWei(); - vm.stopPrank(); - - - IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter(); - vm.roll(block.number + delayedWithdrawalRouter.withdrawalDelayBlocks() + 1); - - delayedWithdrawalRouter.claimDelayedWithdrawals(address(stakingNodeInstance), type(uint256).max); - - uint256 balanceBeforeClaim = address(consensusLayerReceiver).balance; - + beaconChain.advanceEpoch_NoRewards(); + // Start checkpoint vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.processDelayedWithdrawals(); - uint256 balanceAfterClaim = address(consensusLayerReceiver).balance; - uint256 rewardsAmount = balanceAfterClaim - balanceBeforeClaim; + stakingNodeInstance.startCheckpoint(true); + // Checkpoint ends since no active validators are here - // assertEq(stakingNodeInstance.getETHBalance(), depositAmount, "StakingNode ETH balance does not match expected value"); - assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value"); + // Get final pod owner shares + int256 finalPodOwnerShares = eigenPodManager.podOwnerShares(address(stakingNodeInstance)); + // Assert that the increase matches the swept rewards + assertEq(uint256(finalPodOwnerShares), rewardsSweeped, "Pod owner shares increase should match swept rewards"); } } -contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { +contract StakingNodeDelegation is StakingNodeTestBase { using stdStorage for StdStorage; using BytesLib for bytes; - address newMockStakingNodeImplementation; - function setUp() public override { super.setUp(); - // Set the implementation of the StakingNode to be MockStakingNode - newMockStakingNodeImplementation = address(new MockStakingNode()); - vm.prank(actors.admin.STAKING_ADMIN); - stakingNodesManager.upgradeStakingNodeImplementation(newMockStakingNodeImplementation); - } - - function skiptestVerifyWithdrawalCredentialsRevertingWhenPaused() public { - - ProofUtils proofUtils = new ProofUtils(DEFAULT_PROOFS_PATH); - - uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); - - uint64 oracleTimestamp = uint64(block.timestamp); - - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); - - uint40[] memory validatorIndexes = new uint40[](1); - - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); - - bytes[] memory validatorFieldsProofs = proofUtils._getValidatorFieldsProof(); - - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); - - uint256 shares = strategyManager.stakerStrategyShares(address(stakingNodeInstance), stakingNodeInstance.beaconChainETHStrategy()); - assertEq(shares, depositAmount, "Shares do not match deposit amount"); - - vm.expectRevert("Pausable: index is paused"); - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); - } - - function testCreateEigenPodReturnsEigenPodAddressAfterCreated() public { - vm.prank(actors.ops.STAKING_NODE_CREATOR); - IStakingNode stakingNodeInstance = stakingNodesManager.createStakingNode(); - IEigenPod eigenPodInstance = stakingNodeInstance.eigenPod(); - assertEq(address(eigenPodInstance), address(stakingNodeInstance.eigenPod())); - } - - function testClaimDelayedWithdrawals() public { - - vm.prank(actors.ops.STAKING_NODE_CREATOR); - IStakingNode stakingNodeInstance = stakingNodesManager.createStakingNode(); - - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - vm.expectRevert(); - stakingNodeInstance.processDelayedWithdrawals(); } function testDelegateFailWhenNotAdmin() public { @@ -422,7 +128,7 @@ contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { vm.prank(operator); delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ - earningsReceiver: operator, + __deprecated_earningsReceiver: address(1), // unused delegationApprover: address(0), stakerOptOutWindowBlocks: 1 }), @@ -448,7 +154,7 @@ contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { // Register as operator and delegate delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ - earningsReceiver: address(this), + __deprecated_earningsReceiver: address(1), delegationApprover: address(0), stakerOptOutWindowBlocks: 1 }), @@ -487,7 +193,7 @@ contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { vm.prank(operators[i]); delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ - earningsReceiver: operators[i], + __deprecated_earningsReceiver: address(1), delegationApprover: address(0), stakerOptOutWindowBlocks: 1 }), @@ -524,318 +230,544 @@ contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { function testImplementViewFunction() public { vm.prank(actors.ops.STAKING_NODE_CREATOR); IStakingNode stakingNodeInstance = stakingNodesManager.createStakingNode(); - assertEq(stakingNodeInstance.implementation(), address(newMockStakingNodeImplementation)); + address expectedImplementation = address(stakingNodesManager.upgradeableBeacon().implementation()); + assertEq(stakingNodeInstance.implementation(), expectedImplementation, "Implementation address mismatch"); } +} - function testVerifyWithdrawalCredentialsWithWrongWithdrawalAddress() public { - - ProofUtils proofUtils = new ProofUtils(DEFAULT_PROOFS_PATH); +contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase { + address user = vm.addr(156737); - uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); + uint40[] validatorIndices; + uint256 AMOUNT = 32 ether; - uint64 oracleTimestamp = uint64(block.timestamp); - MockEigenLayerBeaconOracle mockBeaconOracle = new MockEigenLayerBeaconOracle(); + function setUp() public override { + super.setUp(); - address eigenPodManagerOwner = OwnableUpgradeable(address(eigenPodManager)).owner(); - vm.prank(eigenPodManagerOwner); - eigenPodManager.updateBeaconChainOracle(IBeaconChainOracle(address(mockBeaconOracle))); + // Create a user address and fund it with 1000 ETH + vm.deal(user, 1000 ether); - bytes32 latestBlockRoot = proofUtils.getLatestBlockRoot(); - mockBeaconOracle.setOracleBlockRootAtTimestamp(latestBlockRoot); + yneth.depositETH{value: 1000 ether}(user); + } + + function testVerifyWithdrawalCredentialsForOneValidator() public { - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); + uint256 nodeId = createStakingNodes(1)[0]; + // Call createValidators with the nodeIds array and validatorCount + validatorIndices = createValidators(repeat(nodeId, 1), 1); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, 1)); - uint40[] memory validatorIndexes = new uint40[](1); + + // Capture state before verification + StateSnapshot memory before = takeSnapshot(nodeId); - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); + _verifyWithdrawalCredentials(nodeId, validatorIndices[0]); - bytes[] memory validatorFieldsProofs = new bytes[](1); - validatorFieldsProofs[0] = proofUtils._getValidatorFieldsProof()[0]; + // Capture state after verification + StateSnapshot memory afterVerification = takeSnapshot(nodeId); - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); + // Assert that ynETH totalAssets, totalSupply, and staking Node balance, queuedShares and withdrawnETH stay the same + assertEq(afterVerification.totalAssets, before.totalAssets, "Total assets should not change"); + assertEq(afterVerification.totalSupply, before.totalSupply, "Total supply should not change"); + assertEq(afterVerification.stakingNodeBalance, before.stakingNodeBalance, "Staking node balance should not change"); + assertEq(afterVerification.queuedShares, before.queuedShares, "Queued shares should not change"); + assertEq(afterVerification.withdrawnETH, before.withdrawnETH, "Withdrawn ETH should not change"); - // address eigenPodAddress = address(stakingNodeInstance.eigenPod()); - // validatorFields[0][1] = (abi.encodePacked(bytes1(uint8(1)), bytes11(0), eigenPodAddress)).toBytes32(0); + // Assert that unverifiedStakedETH decreases + assertLt(afterVerification.unverifiedStakedETH, before.unverifiedStakedETH, "Unverified staked ETH should decrease"); - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - vm.expectRevert("EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); + // Additional checks + assertEq(afterVerification.unverifiedStakedETH, 0, "Unverified staked ETH should be 0 after verification"); + assertEq(uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), AMOUNT, "Pod owner shares should equal AMOUNT"); } - function verifyWithdrawalCredentialsSuccesfullyForProofFile(string memory path) public { - - setJSON(path); - - uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); + function testVerifyWithdrawalCredentialsTwice() public { + uint256 nodeId = createStakingNodes(1)[0]; + // Call createValidators with the nodeIds array and validatorCount + validatorIndices = createValidators(repeat(nodeId, 1), 1); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, 1)); + + uint40 validatorIndex = validatorIndices[0]; - uint64 oracleTimestamp = uint64(block.timestamp); - MockEigenLayerBeaconOracle mockBeaconOracle = new MockEigenLayerBeaconOracle(); + // First verification + _verifyWithdrawalCredentials(nodeId, validatorIndex); - address eigenPodManagerOwner = OwnableUpgradeable(address(eigenPodManager)).owner(); - vm.prank(eigenPodManagerOwner); - eigenPodManager.updateBeaconChainOracle(IBeaconChainOracle(address(mockBeaconOracle))); + // Try to verify withdrawal credentials again + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndex; - // set existing EigenPod to be the EigenPod of the StakingNode for the - // purpose of testing verifyWithdrawalCredentials - address eigenPodAddress = getWithdrawalAddress(); + CredentialProofs memory _proofs = beaconChain.getCredentialProofs(_validators); + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + IEigenPodSimplified node = IEigenPodSimplified(address(stakingNodesManager.nodes(nodeId))); + vm.expectRevert("EigenPod._verifyWithdrawalCredentials: validator must be inactive to prove withdrawal credentials"); + node.verifyWithdrawalCredentials({ + beaconTimestamp: _proofs.beaconTimestamp, + stateRootProof: _proofs.stateRootProof, + validatorIndices: _validators, + validatorFieldsProofs: _proofs.validatorFieldsProofs, + validatorFields: _proofs.validatorFields + }); + vm.stopPrank(); + } - MockStakingNode(payable(address(stakingNodeInstance))) - .setEigenPod(IEigenPod(eigenPodAddress)); + function testVerifyCheckpointsForOneValidator() public { + uint256 nodeId = createStakingNodes(1)[0]; + // Call createValidators with the nodeIds array and validatorCount + validatorIndices = createValidators(repeat(nodeId, 1), 1); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, 1)); + + uint40 validatorIndex = validatorIndices[0]; { - // Upgrade the implementation of EigenPod to be able to alter its owner - EigenPod existingEigenPod = EigenPod(payable(address(stakingNodeInstance.eigenPod()))); - - MockEigenPod mockEigenPod = new MockEigenPod( - IETHPOSDeposit(existingEigenPod.ethPOS()), - IDelayedWithdrawalRouter(address(delayedWithdrawalRouter)), - IEigenPodManager(address(eigenPodManager)), - existingEigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(), - existingEigenPod.GENESIS_TIME() - ); + _verifyWithdrawalCredentials(nodeId, validatorIndex); - address mockEigenPodAddress = address(mockEigenPod); - IEigenPodManager eigenPodManagerInstance = IEigenPodManager(eigenPodManager); - address eigenPodBeaconAddress = address(eigenPodManagerInstance.eigenPodBeacon()); - UpgradeableBeacon eigenPodBeacon = UpgradeableBeacon(eigenPodBeaconAddress); - address eigenPodBeaconOwner = Ownable(eigenPodBeaconAddress).owner(); - vm.prank(eigenPodBeaconOwner); - eigenPodBeacon.upgradeTo(mockEigenPodAddress); + // check that unverifiedStakedETH is 0 and podOwnerShares is 32 ETH (AMOUNT) + assertEq(stakingNodesManager.nodes(nodeId).unverifiedStakedETH(), 0, "_testVerifyWithdrawalCredentials: E0"); + assertEq(uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), AMOUNT, "_testVerifyWithdrawalCredentials: E1"); } - MockEigenPod mockEigenPodInstance = MockEigenPod(payable(address(stakingNodeInstance.eigenPod()))); - mockEigenPodInstance.setPodOwner(address(stakingNodeInstance)); + beaconChain.advanceEpoch(); + // Capture initial state + StateSnapshot memory initialState = takeSnapshot(nodeId); + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(nodeId).startCheckpoint(true); + vm.stopPrank(); + + // make sure startCheckpoint cant be called again, which means that the checkpoint has started + IStakingNode _node = stakingNodesManager.nodes(nodeId); + vm.expectRevert("EigenPod._startCheckpoint: must finish previous checkpoint before starting another"); + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + _node.startCheckpoint(true); + } - ValidatorProofs memory validatorProofs = getWithdrawalCredentialParams(); - bytes32 validatorPubkeyHash = BeaconChainProofs.getPubkeyHash(validatorProofs.validatorFields[0]); - IEigenPod.ValidatorInfo memory zeroedValidatorInfo = IEigenPod.ValidatorInfo({ - validatorIndex: 0, - restakedBalanceGwei: 0, - mostRecentBalanceUpdateTimestamp: 0, - status: IEigenPod.VALIDATOR_STATUS.INACTIVE - }); - mockEigenPodInstance.setValidatorInfo(validatorPubkeyHash, zeroedValidatorInfo); + // Assert that state remains unchanged after starting checkpoint + StateSnapshot memory afterStartCheckpoint = takeSnapshot(nodeId); + assertEq(afterStartCheckpoint.totalAssets, initialState.totalAssets, "Total assets changed after starting checkpoint"); + assertEq(afterStartCheckpoint.totalSupply, initialState.totalSupply, "Total supply changed after starting checkpoint"); + assertEq(afterStartCheckpoint.stakingNodeBalance, initialState.stakingNodeBalance, "Node balance changed after starting checkpoint"); + assertEq(afterStartCheckpoint.queuedShares, initialState.queuedShares, "Queued shares changed after starting checkpoint"); + assertEq(afterStartCheckpoint.withdrawnETH, initialState.withdrawnETH, "Withdrawn ETH changed after starting checkpoint"); + assertEq(afterStartCheckpoint.unverifiedStakedETH, initialState.unverifiedStakedETH, "Unverified staked ETH changed after starting checkpoint"); + // verify checkpoints { - // Upgrade the implementation of EigenPod to be able to alter the owner of the pod being tested - MockEigenPodManager mockEigenPodManager = new MockEigenPodManager(EigenPodManager(address(eigenPodManager))); - address payable eigenPodManagerPayable = payable(address(eigenPodManager)); - ITransparentUpgradeableProxy eigenPodManagerProxy = ITransparentUpgradeableProxy(eigenPodManagerPayable); - - address proxyAdmin = Utils.getTransparentUpgradeableProxyAdminAddress(eigenPodManagerPayable); - vm.prank(proxyAdmin); - eigenPodManagerProxy.upgradeTo(address(mockEigenPodManager)); + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndex; + IStakingNode _node = stakingNodesManager.nodes(nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(_validators, _node.eigenPod().currentCheckpointTimestamp()); + IEigenPodSimplified(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); + + // check that proofsRemaining is 0 + IEigenPod.Checkpoint memory _checkpoint = stakingNodesManager.nodes(nodeId).eigenPod().currentCheckpoint(); + assertEq(_checkpoint.proofsRemaining, 0, "_testVerifyCheckpointsBeforeWithdrawalRequest: E0"); + + // Assert that node balance and shares increased by the amount of rewards + StateSnapshot memory afterVerification = takeSnapshot(nodeId); + uint256 rewardsAmount = uint256(afterVerification.podOwnerShares - initialState.podOwnerShares); + // Calculate expected rewards for one epoch + uint256 expectedRewards = 1 * 1 * 1e9; // 1 GWEI per Epoch per Validator; + assertApproxEqAbs(rewardsAmount, expectedRewards, 1, "Rewards amount does not match expected value for one epoch"); + + assertEq(afterVerification.stakingNodeBalance, initialState.stakingNodeBalance + rewardsAmount, "Node balance did not increase by rewards amount"); + + // Assert that other state variables remain unchanged + assertEq(afterVerification.totalAssets, initialState.totalAssets + expectedRewards, "Total assets changed after verification"); + assertEq(afterVerification.totalSupply, initialState.totalSupply, "Total supply changed after verification"); + assertEq(afterVerification.queuedShares, initialState.queuedShares, "Queued shares changed after verification"); + assertEq(afterVerification.withdrawnETH, initialState.withdrawnETH, "Withdrawn ETH changed after verification"); + assertEq(afterVerification.unverifiedStakedETH, initialState.unverifiedStakedETH, "Unverified staked ETH changed after verification"); } + } - MockEigenPodManager mockEigenPodManagerInstance = MockEigenPodManager(address(eigenPodManager)); - mockEigenPodManagerInstance.setHasPod(address(stakingNodeInstance), stakingNodeInstance.eigenPod()); - bytes32 latestBlockRoot = _getLatestBlockRoot(); - mockBeaconOracle.setOracleBlockRootAtTimestamp(latestBlockRoot); + function testVerifyCheckpointsForManyValidators() public { + uint256 validatorCount = 3; - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - validatorProofs.stateRootProof, - validatorProofs.validatorIndices, - validatorProofs.withdrawalCredentialProofs, - validatorProofs.validatorFields - ); + uint256 nodeId = createStakingNodes(1)[0]; + // Call createValidators with the nodeIds array and validatorCount + validatorIndices = createValidators(repeat(nodeId, 1), validatorCount); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, validatorCount)); - int256 expectedShares = int256(uint256(BeaconChainProofs.getEffectiveBalanceGwei(validatorProofs.validatorFields[0])) * 1e9); - int256 actualShares = eigenPodManager.podOwnerShares(address(stakingNodeInstance)); - assertEq(actualShares, expectedShares, "Staking node shares do not match expected shares"); - } - - function testVerifyWithdrawalCredentialsSuccesfully_32ETH() public { - if (block.chainid != 1) { - return; // Skip test if not on Ethereum Mainnet + { + for (uint256 i = 0; i < validatorIndices.length; i++) { + _verifyWithdrawalCredentials(nodeId, validatorIndices[i]); + } + + // check that unverifiedStakedETH is 0 and podOwnerShares is 32 ETH (AMOUNT) + assertEq(stakingNodesManager.nodes(nodeId).unverifiedStakedETH(), 0, "_testVerifyWithdrawalCredentials: E0"); + assertEq( + uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), + AMOUNT * validatorCount, + "_testVerifyWithdrawalCredentials: E1" + ); } - verifyWithdrawalCredentialsSuccesfullyForProofFile("test/data/ValidatorFieldsProof_1293592_8746783.json"); - } - function testVerifyWithdrawalCredentialsSuccesfully_1ETH() public { - if (block.chainid != 1) { - return; // Skip test if not on Ethereum Mainnet + beaconChain.advanceEpoch_NoRewards(); + + uint256 exitedValidatorsCount = 1; + + // exit validators + { + for (uint256 i = 0; i < exitedValidatorsCount; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + beaconChain.advanceEpoch_NoRewards(); } - verifyWithdrawalCredentialsSuccesfullyForProofFile("test/data/ValidatorFieldsProof_1293592_8654000.json"); - } + // Take snapshot before starting checkpoint + StateSnapshot memory beforeStart = takeSnapshot(nodeId); - function skiptestVerifyWithdrawalCredentialsWithStrategyUnpaused() public { + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(nodeId).startCheckpoint(true); + vm.stopPrank(); + + // make sure startCheckpoint cant be called again, which means that the checkpoint has started + IStakingNode _node = stakingNodesManager.nodes(nodeId); + vm.expectRevert("EigenPod._startCheckpoint: must finish previous checkpoint before starting another"); + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + _node.startCheckpoint(true); + } - ProofUtils proofUtils = new ProofUtils(DEFAULT_PROOFS_PATH); + // Take snapshot after starting checkpoint + StateSnapshot memory afterStart = takeSnapshot(nodeId); - uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); + // Assert state after starting checkpoint + assertEq(afterStart.totalAssets, beforeStart.totalAssets, "Total assets should not change after starting checkpoint"); + assertEq(afterStart.totalSupply, beforeStart.totalSupply, "Total supply should not change after starting checkpoint"); + assertEq(afterStart.stakingNodeBalance, beforeStart.stakingNodeBalance, "Staking node balance should not change after starting checkpoint"); + assertEq(afterStart.queuedShares, beforeStart.queuedShares, "Queued shares should not change after starting checkpoint"); + assertEq(afterStart.withdrawnETH, beforeStart.withdrawnETH, "Withdrawn ETH should not change after starting checkpoint"); + assertEq(afterStart.unverifiedStakedETH, beforeStart.unverifiedStakedETH, "Unverified staked ETH should not change after starting checkpoint"); + assertEq(afterStart.podOwnerShares, beforeStart.podOwnerShares, "Pod owner shares should not change after starting checkpoint"); - uint64 oracleTimestamp = uint64(block.timestamp); + // verify checkpoints + { + uint40[] memory _validators = validatorIndices; + IStakingNode _node = stakingNodesManager.nodes(nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(_validators, _node.eigenPod().currentCheckpointTimestamp()); + IEigenPodSimplified(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); + // Take snapshot after verifying checkpoint + StateSnapshot memory afterVerify = takeSnapshot(nodeId); + + // Assert state after verifying checkpoint + assertEq(afterVerify.totalAssets, afterStart.totalAssets, "Total assets should not change after verifying checkpoint"); + assertEq(afterVerify.totalSupply, afterStart.totalSupply, "Total supply should not change after verifying checkpoint"); + assertGe(afterVerify.stakingNodeBalance, afterStart.stakingNodeBalance, "Staking node balance should not decrease after verifying checkpoint"); + assertEq(afterVerify.queuedShares, afterStart.queuedShares, "Queued shares should not change after verifying checkpoint"); + assertEq(afterVerify.withdrawnETH, afterStart.withdrawnETH, "Withdrawn ETH should not change after verifying checkpoint"); + assertEq(afterVerify.unverifiedStakedETH, afterStart.unverifiedStakedETH, "Unverified staked ETH should not change after verifying checkpoint"); + assertGe(afterVerify.podOwnerShares, afterStart.podOwnerShares, "Pod owner shares should not decrease after verifying checkpoint"); + + IEigenPod.Checkpoint memory _checkpoint = stakingNodesManager.nodes(nodeId).eigenPod().currentCheckpoint(); + assertEq(_checkpoint.proofsRemaining, 0, "_testVerifyCheckpointsBeforeWithdrawalRequest: E0"); + assertApproxEqAbs( + uint256(eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId)))), + AMOUNT * validatorCount, + 1000000000, + "_testVerifyCheckpointsBeforeWithdrawalRequest: E1" + ); + } + } +} - uint40[] memory validatorIndexes = new uint40[](1); +contract StakingNodeWithdrawals is StakingNodeTestBase { - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); + function testQueueWithdrawals() public { - bytes[] memory validatorFieldsProofs = new bytes[](1); - validatorFieldsProofs[0] = proofUtils._getValidatorFieldsProof()[0]; + // Setup + uint256 depositAmount = 32 ether; + address user = vm.addr(156737); + vm.deal(user, 1000 ether); + yneth.depositETH{value: depositAmount}(user); - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); + uint256[] memory nodeIds = createStakingNodes(1); + uint256 nodeId = nodeIds[0]; + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeIds[0]); + // Create and register a validator + uint40[] memory validatorIndices = createValidators(repeat(nodeIds[0], 1), 1); + + registerValidators(repeat(nodeIds[0], 1)); + beaconChain.advanceEpoch_NoRewards(); - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); + // Verify withdrawal credentials + _verifyWithdrawalCredentials(nodeIds[0], validatorIndices[0]); - uint256 shares = strategyManager.stakerStrategyShares( - address(stakingNodeInstance), - stakingNodeInstance.beaconChainETHStrategy() - ); - assertEq(shares, depositAmount, "Shares do not match deposit amount"); + // Simulate some rewards + beaconChain.advanceEpoch(); + + uint40[] memory _validators = new uint40[](1); + _validators[0] = validatorIndices[0]; + + startAndVerifyCheckpoint(nodeId, _validators); + + // Get initial state + StateSnapshot memory initialState = takeSnapshot(nodeIds[0]); + + // Queue withdrawals + uint256 withdrawalAmount = 1 ether; + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); + + // Get final state + StateSnapshot memory finalState = takeSnapshot(nodeIds[0]); + + // Assert + assertEq(finalState.totalAssets, initialState.totalAssets, "Total assets should remain unchanged"); + assertEq(finalState.totalSupply, initialState.totalSupply, "Total supply should remain unchanged"); + assertEq(finalState.stakingNodeBalance, initialState.stakingNodeBalance, "Staking node balance should remain unchanged"); + assertEq(finalState.queuedShares, initialState.queuedShares + withdrawalAmount, "Queued shares should increase by withdrawal amount"); + assertEq(finalState.withdrawnETH, initialState.withdrawnETH, "Withdrawn ETH should remain unchanged"); + assertEq(finalState.unverifiedStakedETH, initialState.unverifiedStakedETH, "Unverified staked ETH should remain unchanged"); + assertEq(finalState.podOwnerShares, initialState.podOwnerShares - int256(withdrawalAmount), "Pod owner shares should decrease by withdrawalAmount"); + } + + function testQueueWithdrawalsFailsWhenNotAdmin() public { + uint256[] memory nodeIds = createStakingNodes(1); + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeIds[0]); + + uint256 withdrawalAmount = 1 ether; + vm.prank(address(0x1234567890123456789012345678901234567890)); + vm.expectRevert(StakingNode.NotStakingNodesWithdrawer.selector); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); } - function skiptestVerifyWithdrawalCredentialsMismatchedValidatorIndexAndProofsLengths() public { + function testQueueWithdrawalsFailsWhenInsufficientBalance() public { + uint256[] memory nodeIds = createStakingNodes(1); + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeIds[0]); - ProofUtils proofUtils = new ProofUtils(DEFAULT_PROOFS_PATH); + uint256 withdrawalAmount = 100 ether; // Assuming this is more than the node's balance + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + vm.expectRevert("EigenPodManager.removeShares: cannot result in pod owner having negative shares"); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); + } + function testCompleteQueuedWithdrawalsWithMultipleValidators() public { + // Setup + uint256 validatorCount = 2; uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); + address user = vm.addr(156737); + vm.deal(user, 1000 ether); + yneth.depositETH{value: depositAmount * validatorCount}(user); // Deposit for validators - uint64 oracleTimestamp = uint64(block.timestamp); + uint256[] memory nodeIds = createStakingNodes(1); + uint256 nodeId = nodeIds[0]; + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeId); + + // Setup: Create multiple validators and verify withdrawal credentials + uint40[] memory validatorIndices = createValidators(repeat(nodeId, validatorCount), validatorCount); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, validatorCount)); - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); + beaconChain.advanceEpoch_NoRewards(); - uint40[] memory validatorIndexes = new uint40[](1); + for (uint256 i = 0; i < validatorCount; i++) { + _verifyWithdrawalCredentials(nodeIds[0], validatorIndices[i]); + } - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); + beaconChain.advanceEpoch_NoRewards(); - bytes[] memory validatorFieldsProofs = new bytes[](1); - validatorFieldsProofs[0] = proofUtils._getValidatorFieldsProof()[0]; + // Exit some validators + uint256 exitedValidatorCount = 1; + for (uint256 i = 0; i < exitedValidatorCount; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + + // Advance the beacon chain by one epoch without rewards + beaconChain.advanceEpoch_NoRewards(); - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); + // Start and verify checkpoint for all validators + startAndVerifyCheckpoint(nodeId, validatorIndices); - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); + // Queue withdrawals for exited validators + uint256 withdrawalAmount = 32 ether * exitedValidatorCount; + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); + + // Capture initial state + StateSnapshot memory before = takeSnapshot(nodeIds[0]); + + QueuedWithdrawalInfo[] memory queuedWithdrawals = new QueuedWithdrawalInfo[](1); + queuedWithdrawals[0] = QueuedWithdrawalInfo({ + withdrawnAmount: withdrawalAmount + }); + _completeQueuedWithdrawals(queuedWithdrawals, nodeIds[0]); + + // Capture final state + StateSnapshot memory afterCompletion = takeSnapshot(nodeIds[0]); + + // Assertions + assertEq(afterCompletion.queuedShares, before.queuedShares - withdrawalAmount, "Queued shares should decrease"); + assertEq(afterCompletion.withdrawnETH, before.withdrawnETH + withdrawalAmount, "Withdrawn ETH should increase"); + assertEq(afterCompletion.podOwnerShares, before.podOwnerShares, "Pod owner shares should remain unchanged"); + assertEq(afterCompletion.stakingNodeBalance, before.stakingNodeBalance, "Staking node balance should remain unchanged"); } - event LogUintMessage(string message, uint256 value); - event LogAddressMessage(string message, address value); - event LogBytesMessage(string message, bytes value); + function testCompleteQueuedWithdrawalsWithSlashedValidators() public { + uint256 validatorCount = 2; - function skiptestVerifyWithdrawalCredentialsMismatchedProofsAndValidatorFieldsLengths() public { + { + // Setup + uint256 depositAmount = 32 ether; + address user = vm.addr(156737); + vm.deal(user, 1000 ether); + yneth.depositETH{value: depositAmount * validatorCount}(user); // Deposit for validators + } + + uint256 nodeId = createStakingNodes(1)[0]; + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeId); + + // Setup: Create multiple validators and verify withdrawal credentials + uint40[] memory validatorIndices = createValidators(repeat(nodeId, validatorCount), validatorCount); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, validatorCount)); - ProofUtils proofUtils = new ProofUtils(DEFAULT_PROOFS_PATH); + beaconChain.advanceEpoch_NoRewards(); - uint256 depositAmount = 32 ether; - (IStakingNode stakingNodeInstance,) = setupStakingNode(depositAmount); + for (uint256 i = 0; i < validatorCount; i++) { + _verifyWithdrawalCredentials(nodeId, validatorIndices[i]); + } - uint64 oracleTimestamp = uint64(block.timestamp); + beaconChain.advanceEpoch_NoRewards(); - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); + uint256 slashedValidatorCount = 1; + // Slash some validators + uint40[] memory slashedValidators = new uint40[](slashedValidatorCount); + for (uint256 i = 0; i < slashedValidatorCount; i++) { + slashedValidators[i] = validatorIndices[i]; + } + beaconChain.slashValidators(slashedValidators); - uint40[] memory validatorIndexes = new uint40[](1); + beaconChain.advanceEpoch_NoRewards(); - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); + // Exit remaining validators + uint256 exitedValidatorCount = validatorCount - slashedValidatorCount; + for (uint256 i = slashedValidatorCount; i < validatorCount; i++) { + beaconChain.exitValidator(validatorIndices[i]); + } + + // Advance the beacon chain by one epoch without rewards + beaconChain.advanceEpoch_NoRewards(); - bytes[] memory validatorFieldsProofs = proofUtils._getValidatorFieldsProof(); + // Capture initial state + StateSnapshot memory before = takeSnapshot(nodeId); - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); + // Start and verify checkpoint for all validators + startAndVerifyCheckpoint(nodeId, validatorIndices); - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNodeInstance.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); - } -} + // Calculate expected withdrawal amount (slashed validators lose 1 ETH each) + uint256 withdrawalAmount = (32 ether * exitedValidatorCount); + + // Queue withdrawals for all validators + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); -contract StakingNodeStakedETHAllocationTests is StakingNodeTestBase { + QueuedWithdrawalInfo[] memory queuedWithdrawals = new QueuedWithdrawalInfo[](1); + queuedWithdrawals[0] = QueuedWithdrawalInfo({ + withdrawnAmount: withdrawalAmount + }); + _completeQueuedWithdrawals(queuedWithdrawals, nodeId); - event AllocatedStakedETH(uint256 previousAmount, uint256 newAmount); + // Capture final state + StateSnapshot memory afterCompletion = takeSnapshot(nodeId); - function testAllocateStakedETH() public { - (IStakingNode stakingNodeInstance,) = setupStakingNode(32 ether); - uint256 initialETHBalance = stakingNodeInstance.getETHBalance(); - uint256 amountToAllocate = 10 ether; + uint256 slashedAmount = slashedValidatorCount * (beaconChain.SLASH_AMOUNT_GWEI() * 1e9); - vm.expectEmit(true, true, false, true); - emit AllocatedStakedETH(initialETHBalance, amountToAllocate); - vm.prank(address(stakingNodesManager)); - stakingNodeInstance.allocateStakedETH(amountToAllocate); + // Assertions + assertEq(afterCompletion.withdrawnETH, before.withdrawnETH + withdrawalAmount, "Withdrawn ETH should increase by the withdrawn amount"); + assertEq( + afterCompletion.podOwnerShares, + before.podOwnerShares - int256(slashedAmount) - int256(withdrawalAmount), + "Pod owner shares should decrease by SLASH_AMOUNT_GWEI per slashed validator and by withdrawalAmount" + ); + assertEq(afterCompletion.stakingNodeBalance, before.stakingNodeBalance - slashedAmount, "Staking node balance should remain unchanged"); - uint256 newETHBalance = stakingNodeInstance.getETHBalance(); - assertEq(newETHBalance, initialETHBalance + amountToAllocate, "ETH balance did not increase by the allocated amount"); + // Verify that the total withdrawn amount matches the expected amount + assertEq(afterCompletion.withdrawnETH - before.withdrawnETH, withdrawalAmount, "Total withdrawn amount should match the expected amount"); } - function testAllocateStakedETHFailsWhenNotStakingNodesManager() public { - (IStakingNode stakingNodeInstance,) = setupStakingNode(32 ether); - uint256 amountToAllocate = 10 ether; + function testQueueWithdrawalsBeforeExitingAndVerifyingValidator() public { + uint256 validatorCount = 1; + uint256 depositAmount = 32 ether; + address user = vm.addr(156737); + vm.deal(user, 1000 ether); + yneth.depositETH{value: depositAmount * validatorCount}(user); - vm.expectRevert(StakingNode.NotStakingNodesManager.selector); - vm.prank(actors.eoa.DEFAULT_SIGNER); - stakingNodeInstance.allocateStakedETH(amountToAllocate); - } + uint256 nodeId = createStakingNodes(1)[0]; + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeId); + + // Create and register a validator + uint40[] memory validatorIndices = createValidators(repeat(nodeId, validatorCount), validatorCount); + beaconChain.advanceEpoch_NoRewards(); + registerValidators(repeat(nodeId, validatorCount)); - function testGetETHBalanceWithAllocationAndEigenPodDeposit() public { - (IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(32 ether); - uint256 initialETHBalance = stakingNodeInstance.getETHBalance(); - uint256 amountToAllocate = 10 ether; - uint256 amountToDepositInEigenPod = 5 ether; + beaconChain.advanceEpoch_NoRewards(); - // Allocate ETH to the staking node - vm.prank(address(stakingNodesManager)); - stakingNodeInstance.allocateStakedETH(amountToAllocate); + // Verify withdrawal credentials + _verifyWithdrawalCredentials(nodeId, validatorIndices[0]); - // Deposit ETH directly to the EigenPod - address payable eigenPodAddress = payable(address(eigenPodInstance)); - vm.deal(address(this), amountToDepositInEigenPod); - (bool success,) = eigenPodAddress.call{value: amountToDepositInEigenPod}(""); - require(success, "Failed to send ETH to EigenPod"); + beaconChain.advanceEpoch_NoRewards(); - uint256 expectedETHBalance = initialETHBalance + amountToAllocate; - uint256 actualETHBalance = stakingNodeInstance.getETHBalance(); + // Capture initial state + StateSnapshot memory before = takeSnapshot(nodeId); - assertEq(actualETHBalance, expectedETHBalance, "ETH balance does not match expected value after allocation and EigenPod deposit"); - } -} + // Queue withdrawals before exiting the validator + uint256 withdrawalAmount = 32 ether; + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodeInstance.queueWithdrawals(withdrawalAmount); + // Exit the validator + beaconChain.slashValidators(validatorIndices); + + beaconChain.advanceEpoch_NoRewards(); -contract StakingNodeMiscTests is StakingNodeTestBase { + // Start and verify checkpoint + startAndVerifyCheckpoint(nodeId, validatorIndices); - function testSendingETHToStakingNodeShouldRevert() public { - (IStakingNode stakingNodeInstance,) = setupStakingNode(32 ether); - uint256 amountToSend = 1 ether; + // Assert that podOwnerShares are equal to negative slashingAmount + uint256 slashedAmount = beaconChain.SLASH_AMOUNT_GWEI() * 1e9; + assertEq( + eigenPodManager.podOwnerShares(address(stakingNodeInstance)), + -int256(slashedAmount), + "Pod owner shares should be equal to negative slashing amount" + ); - // Attempt to send ETH to the StakingNode contract - (bool sent, ) = address(stakingNodeInstance).call{value: amountToSend}(""); - assertFalse(sent, "Sending ETH should fail"); + // Complete queued withdrawals + QueuedWithdrawalInfo[] memory queuedWithdrawals = new QueuedWithdrawalInfo[](1); + queuedWithdrawals[0] = QueuedWithdrawalInfo({ + withdrawnAmount: withdrawalAmount + }); + _completeQueuedWithdrawals(queuedWithdrawals, nodeId); + + // Capture final state + StateSnapshot memory afterCompletion = takeSnapshot(nodeId); + + // Assertions + assertEq(afterCompletion.withdrawnETH, before.withdrawnETH + withdrawalAmount - slashedAmount, "Withdrawn ETH should increase by the withdrawn amount"); + assertEq( + afterCompletion.podOwnerShares, + before.podOwnerShares - int256(withdrawalAmount), + "Pod owner shares should decrease by withdrawalAmount" + ); + assertEq(afterCompletion.queuedShares, before.queuedShares, "Queued shares should decrease back to original value"); + assertEq(afterCompletion.stakingNodeBalance, before.stakingNodeBalance - slashedAmount, "Staking node balance should remain unchanged"); + assertEq(afterCompletion.withdrawnETH , before.withdrawnETH + withdrawalAmount - slashedAmount, "Total withdrawn amount should match the expected amount"); } } diff --git a/test/integration/StakingNodeTestBase.sol b/test/integration/StakingNodeTestBase.sol new file mode 100644 index 000000000..1e6f6864d --- /dev/null +++ b/test/integration/StakingNodeTestBase.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IntegrationBaseTest} from "test/integration/IntegrationBaseTest.sol"; +import {IStakingNode} from "src/interfaces/IStakingNode.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {BeaconChainMock, BeaconChainProofs, CheckpointProofs, CredentialProofs } from "lib/eigenlayer-contracts/src/test/integration/mocks/BeaconChainMock.t.sol"; +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; + +interface IEigenPodSimplified { + function verifyWithdrawalCredentials(uint64 beaconTimestamp, BeaconChainProofs.StateRootProof calldata stateRootProof, uint40[] calldata validatorIndices, bytes[] calldata validatorFieldsProofs, bytes32[][] calldata validatorFields) external; + function verifyCheckpointProofs(BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof, BeaconChainProofs.BalanceProof[] calldata proofs) external; +} + +interface ITransparentUpgradeableProxy { + function upgradeTo(address) external payable; +} + +contract StakingNodeTestBase is IntegrationBaseTest { + + struct QueuedWithdrawalInfo { + uint256 withdrawnAmount; + } + + struct StateSnapshot { + uint256 totalAssets; + uint256 totalSupply; + uint256 stakingNodeBalance; + uint256 queuedShares; + uint256 withdrawnETH; + uint256 unverifiedStakedETH; + int256 podOwnerShares; + } + + function createStakingNodes(uint nodeCount) public returns (uint256[] memory) { + uint256[] memory nodeIds = new uint256[](nodeCount); + for (uint256 i = 0; i < nodeCount; i++) { + vm.prank(actors.ops.STAKING_NODE_CREATOR); + IStakingNode node = stakingNodesManager.createStakingNode(); + nodeIds[i] = node.nodeId(); + } + return nodeIds; + } + + function _verifyWithdrawalCredentials(uint256 _nodeId, uint40 _validatorIndex) internal { + uint40[] memory _validators = new uint40[](1); + _validators[0] = _validatorIndex; + + + CredentialProofs memory _proofs = beaconChain.getCredentialProofs(_validators); + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + IEigenPodSimplified(address(stakingNodesManager.nodes(_nodeId))).verifyWithdrawalCredentials({ + beaconTimestamp: _proofs.beaconTimestamp, + stateRootProof: _proofs.stateRootProof, + validatorIndices: _validators, + validatorFieldsProofs: _proofs.validatorFieldsProofs, + validatorFields: _proofs.validatorFields + }); + vm.stopPrank(); + } + + function takeSnapshot(uint256 nodeId) internal view returns (StateSnapshot memory) { + return StateSnapshot({ + totalAssets: yneth.totalAssets(), + totalSupply: yneth.totalSupply(), + stakingNodeBalance: stakingNodesManager.nodes(nodeId).getETHBalance(), + queuedShares: stakingNodesManager.nodes(nodeId).getQueuedSharesAmount(), + withdrawnETH: stakingNodesManager.nodes(nodeId).getWithdrawnETH(), + unverifiedStakedETH: stakingNodesManager.nodes(nodeId).unverifiedStakedETH(), + podOwnerShares: eigenPodManager.podOwnerShares(address(stakingNodesManager.nodes(nodeId))) + }); + } + + function _completeQueuedWithdrawals(QueuedWithdrawalInfo[] memory queuedWithdrawals, uint256 nodeId) internal { + // create Withdrawal struct + IDelegationManager.Withdrawal[] memory _withdrawals = new IDelegationManager.Withdrawal[](queuedWithdrawals.length); + { + for (uint256 i = 0; i < queuedWithdrawals.length; i++) { + uint256[] memory _shares = new uint256[](1); + _shares[0] = queuedWithdrawals[i].withdrawnAmount; + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + address _stakingNode = address(stakingNodesManager.nodes(nodeId)); + _withdrawals[i] = IDelegationManager.Withdrawal({ + staker: _stakingNode, + delegatedTo: delegationManager.delegatedTo(_stakingNode), + withdrawer: _stakingNode, + nonce: delegationManager.cumulativeWithdrawalsQueued(_stakingNode) - 1, + startBlock: uint32(block.number), + strategies: _strategies, + shares: _shares + }); + } + } + + { + IStrategy[] memory _strategies = new IStrategy[](1); + _strategies[0] = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // beacon chain eth strat + + // advance time to allow completion + vm.roll(block.number + delegationManager.getWithdrawalDelay(_strategies)); + } + + // complete queued withdrawals + { + uint256[] memory _middlewareTimesIndexes = new uint256[](_withdrawals.length); + // all is zeroed out by defailt + _middlewareTimesIndexes[0] = 0; + vm.startPrank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodesManager.nodes(nodeId).completeQueuedWithdrawals(_withdrawals, _middlewareTimesIndexes); + vm.stopPrank(); + } + } + + function startAndVerifyCheckpoint(uint256 nodeId, uint40[] memory _validators) internal { + // start checkpoint + { + vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); + stakingNodesManager.nodes(nodeId).startCheckpoint(true); + vm.stopPrank(); + } + // verify checkpoints + { + IStakingNode _node = stakingNodesManager.nodes(nodeId); + CheckpointProofs memory _cpProofs = beaconChain.getCheckpointProofs(_validators, _node.eigenPod().currentCheckpointTimestamp()); + IEigenPodSimplified(address(_node.eigenPod())).verifyCheckpointProofs({ + balanceContainerProof: _cpProofs.balanceContainerProof, + proofs: _cpProofs.balanceProofs + }); + } + } +} \ No newline at end of file diff --git a/test/integration/StakingNodesManager.t.sol b/test/integration/StakingNodesManager.t.sol index 30990a3d0..906bf4f5b 100644 --- a/test/integration/StakingNodesManager.t.sol +++ b/test/integration/StakingNodesManager.t.sol @@ -14,6 +14,10 @@ import {ynETH} from "src/ynETH.sol"; import {RewardsType} from "src/interfaces/IRewardsDistributor.sol"; import {TestStakingNodeV2} from "test/mocks/TestStakingNodeV2.sol"; import {TestStakingNodesManagerV2} from "test/mocks/TestStakingNodesManagerV2.sol"; +import {StakingNodeTestBase} from "./StakingNodeTestBase.sol"; + +import "forge-std/console.sol"; + contract StakingNodesManagerStakingNodeCreation is IntegrationBaseTest { @@ -130,6 +134,9 @@ contract StakingNodesManagerStakingNodeImplementation is IntegrationBaseTest { ProxyAdmin(getTransparentUpgradeableProxyAdminAddress(address(stakingNodesManager))) .upgradeAndCall(ITransparentUpgradeableProxy(address(stakingNodesManager)), newStakingNodesManagerImpl, ""); + // uint64 initializedVersion = stakingNodeInstance.getInitializedVersion(); + // console.log("Initialized version initializedVersion", initializedVersion); + TestStakingNodeV2 testStakingNodeV2 = new TestStakingNodeV2(); vm.prank(actors.admin.STAKING_ADMIN); stakingNodesManager.upgradeStakingNodeImplementation(payable(testStakingNodeV2)); @@ -531,7 +538,86 @@ contract StakingNodesManagerValidators is IntegrationBaseTest { } } -contract StakingNodeManagerWithdrawals is IntegrationBaseTest { +contract StakingNodeManagerWithdrawals is StakingNodeTestBase { + + uint256 constant STAKING_NODE_COUNT = 1; + uint256 constant VALIDATORS_PER_NODE = 2; + + struct NodeData { + uint40[] validatorIndices; + } + + mapping(uint256 => NodeData) internal nodeData; + + function setUp() public override { + super.setUp(); + + // Create STAKING_NODE_COUNT StakingNodes + for (uint i = 0; i < STAKING_NODE_COUNT; i++) { + vm.prank(actors.ops.STAKING_NODE_CREATOR); + stakingNodesManager.createStakingNode(); + } + + // Deposit 1000 ETH to ynETH + address depositor = vm.addr(100); + vm.deal(depositor, 1000 ether); + vm.prank(depositor); + yneth.depositETH{value: 1000 ether}(depositor); + + + // Create an array of nodeIds + uint256[] memory nodeIds = new uint256[](STAKING_NODE_COUNT); + for (uint nodeId = 0; nodeId < STAKING_NODE_COUNT; nodeId++) { + nodeIds[nodeId] = nodeId; + } + + + uint40[] memory allValidatorIndices = createValidators(nodeIds, VALIDATORS_PER_NODE); + uint256[] memory assignedNodeIds = new uint256[](STAKING_NODE_COUNT * VALIDATORS_PER_NODE); + + // Register validators for each staking node + for (uint nodeId = 0; nodeId < STAKING_NODE_COUNT; nodeId++) { + nodeData[nodeId].validatorIndices = new uint40[](VALIDATORS_PER_NODE); + for (uint i = 0; i < VALIDATORS_PER_NODE; i++) { + nodeData[nodeId].validatorIndices[i] = allValidatorIndices[nodeId * VALIDATORS_PER_NODE + i]; + + assignedNodeIds[nodeId * VALIDATORS_PER_NODE + i] = nodeId; + } + } + beaconChain.advanceEpoch_NoRewards(); + registerValidators(assignedNodeIds); + + // Verify that STAKING_NODE_COUNT * VALIDATORS_PER_NODE validators were registered in total + assertEq(stakingNodesManager.getAllValidators().length, STAKING_NODE_COUNT * VALIDATORS_PER_NODE, "Incorrect number of registered validators"); + } + + function exitValidatorsAndAccumulateRewards( + uint256 nodeId, + uint256 exitCount, + uint256 epochsToAdvance + ) internal returns (uint256 accumulatedRewards, uint256[] memory exitedValidatorIndices) { + + // Advance epochs and accumulate rewards + for (uint256 i = 0; i < epochsToAdvance; i++) { + beaconChain.advanceEpoch(); + } + accumulatedRewards = epochsToAdvance * VALIDATORS_PER_NODE * 1e9; // 1 GWEI per Epoch per Validator; + + // Exit validators + exitedValidatorIndices = new uint256[](exitCount); + for (uint256 i = 0; i < exitCount; i++) { + exitedValidatorIndices[i] = nodeData[nodeId].validatorIndices[i]; + } + + for (uint256 i = 0; i < exitedValidatorIndices.length; i++) { + beaconChain.exitValidator(uint40(exitedValidatorIndices[i])); + } + beaconChain.advanceEpoch_NoRewards(); + + startAndVerifyCheckpoint(nodeId, nodeData[nodeId].validatorIndices); + + beaconChain.advanceEpoch_NoRewards(); + } function testProcessWithdrawnETH() public { address addr1 = vm.addr(100); @@ -545,15 +631,55 @@ contract StakingNodeManagerWithdrawals is IntegrationBaseTest { vm.prank(actors.ops.STAKING_NODE_CREATOR); stakingNodesManager.processRewards(0, RewardsType.ConsensusLayer); } + + // TODO: reenable + function skiptestProcessPrincipalWithdrawalsForThreeNodes() public { + + uint256 nodeId = 0; + uint256 exitedValidatorsCount = 2; + (uint256 rewardsAmount, ) = exitValidatorsAndAccumulateRewards(nodeId, exitedValidatorsCount, 5); + + // Prepare withdrawal actions + StakingNodesManager.WithdrawalAction[] memory actions = new StakingNodesManager.WithdrawalAction[](1); + + uint256 amountToReinvest = 24 ether; + uint256 amountToQueue = exitedValidatorsCount * 32 ether - amountToReinvest; + // nodeId, amountToReinvest, amountToQueue, rewardsAmount + actions[0] = IStakingNodesManager.WithdrawalAction(nodeId, amountToReinvest, amountToQueue, rewardsAmount ); + + // Record balances before processing withdrawals + uint256 ynETHBalanceBefore = address(yneth).balance; + uint256 ynETHRedemptionAssetsVaultBalanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + + // Process principal withdrawals + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.processPrincipalWithdrawals(actions); + + // Verify balances after processing withdrawals + uint256 ynETHBalanceAfter = address(yneth).balance; + uint256 ynETHRedemptionAssetsVaultBalanceAfter = address(ynETHRedemptionAssetsVaultInstance).balance; + + // Calculate net rewards after RewardsDistributor fee + uint256 rewardsDistributorFee = (rewardsAmount * rewardsDistributor.feesBasisPoints()) / 10000; + uint256 netRewards = rewardsAmount - rewardsDistributorFee; + + // Verify net rewards + assertEq(ynETHBalanceAfter - ynETHBalanceBefore, amountToReinvest + netRewards, "Incorrect amount reinvested in ynETH including net rewards"); + assertEq(ynETHRedemptionAssetsVaultBalanceAfter - ynETHRedemptionAssetsVaultBalanceBefore, amountToQueue, "Incorrect amount sent to withdrawal queue"); + } + + } contract StakingNodesManagerMisc is IntegrationBaseTest { - function testSendingETHToStakingNodesManagerShouldRevert() public { + function testSendingETHToStakingNodesManagerShouldNotRevert() public { uint256 amountToSend = 1 ether; // Send ETH to the StakingNodesManager contract (bool sent, ) = address(stakingNodesManager).call{value: amountToSend}(""); - assertFalse(sent, "Sending ETH should fail"); + assertTrue(sent, "ETH transfer failed"); + assertEq(address(stakingNodesManager).balance, amountToSend, "Balance of StakingNodesManager incorrect after receiving ETH"); + } } \ No newline at end of file diff --git a/test/integration/Upgrades.t.sol b/test/integration/Upgrades.t.sol index 2a6562cd5..60b905580 100644 --- a/test/integration/Upgrades.t.sol +++ b/test/integration/Upgrades.t.sol @@ -44,15 +44,6 @@ contract UpgradesTest is IntegrationBaseTest { assertEq(currentRewardsDistributorImpl, newRewardsDistributorImpl); } - // function testUpgradeYnLSD() public { - // address newYnLSDImpl = address(new ynLSD()); - // vm.prank(actors.admin.PROXY_ADMIN_OWNER); - // ProxyAdmin(getTransparentUpgradeableProxyAdminAddress(address(ynlsd))).upgradeAndCall(ITransparentUpgradeableProxy(address(ynlsd)), newYnLSDImpl, ""); - - // address currentYnLSDImpl = getTransparentUpgradeableProxyImplementationAddress(address(ynlsd)); - // assertEq(currentYnLSDImpl, newYnLSDImpl); - // } - function testUpgradeStakingNodesManagerToV2AndReinit() public { TestStakingNodesManagerV2.ReInit memory reInit = TestStakingNodesManagerV2.ReInit({ newV2Value: 42 @@ -64,7 +55,7 @@ contract UpgradesTest is IntegrationBaseTest { .upgradeAndCall( ITransparentUpgradeableProxy(address(stakingNodesManager)), newStakingNodesManagerV2Impl, - abi.encodeWithSelector(TestStakingNodesManagerV2.initializeV2.selector, reInit) + abi.encodeWithSelector(TestStakingNodesManagerV2.initializeV3.selector, reInit) ); address currentStakingNodesManagerImpl = getTransparentUpgradeableProxyImplementationAddress(address(stakingNodesManager)); @@ -120,62 +111,4 @@ contract UpgradesTest is IntegrationBaseTest { vm.prank(address(this)); upgradedYnETH.deposit(nETHDepositAmount, address(this)); } - - // function setupInitializeYnLSD(address assetAddress) internal returns (ynLSD.Init memory, ynLSD ynlsd) { - // TransparentUpgradeableProxy ynlsdProxy; - // ynlsd = ynLSD(payable(ynlsdProxy)); - - // // Re-deploying ynLSD and creating its proxy again - // ynlsd = new ynLSD(); - // ynlsdProxy = new TransparentUpgradeableProxy(address(ynlsd), actors.admin.PROXY_ADMIN_OWNER, ""); - // ynlsd = ynLSD(payable(ynlsdProxy)); - - // address[] memory pauseWhitelist = new address[](1); - // pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; - - // IERC20[] memory assets = new IERC20[](2); - // address[] memory assetsAddresses = new address[](2); - // address[] memory priceFeeds = new address[](2); - // uint256[] memory maxAges = new uint256[](2); - // IStrategy[] memory strategies = new IStrategy[](2); - - // // rETH - // assets[0] = IERC20(chainAddresses.lsd.RETH_ADDRESS); - // assetsAddresses[0] = chainAddresses.lsd.RETH_ADDRESS; - // strategies[0] = IStrategy(chainAddresses.lsd.RETH_STRATEGY_ADDRESS); - // priceFeeds[0] = chainAddresses.lsd.RETH_FEED_ADDRESS; - // maxAges[0] = uint256(86400); - - // // Zero Addresses - // assets[1] = IERC20(assetAddress); - // assetsAddresses[1] = chainAddresses.lsd.STETH_ADDRESS; - // strategies[1] = IStrategy(chainAddresses.lsd.STETH_STRATEGY_ADDRESS); - // priceFeeds[1] = chainAddresses.lsd.STETH_FEED_ADDRESS; - // maxAges[1] = uint256(86400); //one hour - // ynLSD.Init memory init = ynLSD.Init({ - // assets: assets, - // strategies: strategies, - // strategyManager: strategyManager, - // delegationManager: delegationManager, - // oracle: yieldNestOracle, - // maxNodeCount: 10, - // admin: actors.admin.ADMIN, - // stakingAdmin: actors.admin.STAKING_ADMIN, - // lsdRestakingManager: actors.ops.LSD_RESTAKING_MANAGER, - // lsdStakingNodeCreatorRole: actors.ops.STAKING_NODE_CREATOR, - // pauseWhitelist: pauseWhitelist, - // pauser: actors.ops.PAUSE_ADMIN, - // unpauser: actors.admin.UNPAUSE_ADMIN, - // depositBootstrapper: actors.eoa.DEPOSIT_BOOTSTRAPPER - // }); - - // return (init, ynlsd); - // } - - // function testYnLSDInitializeRevertsAssetAddressZero() public { - // (ynLSD.Init memory init, ynLSD ynlsd) = setupInitializeYnLSD(address(0)); - // bytes memory encodedError = abi.encodeWithSelector(ynLSD.ZeroAddress.selector); - // vm.expectRevert(encodedError); - // ynlsd.initialize(init); - // } } diff --git a/test/integration/ynEIGEN/TokenStakingNode.t.sol b/test/integration/ynEIGEN/TokenStakingNode.t.sol index e18a49d4a..1f63d8194 100644 --- a/test/integration/ynEIGEN/TokenStakingNode.t.sol +++ b/test/integration/ynEIGEN/TokenStakingNode.t.sol @@ -121,11 +121,12 @@ contract TokenStakingNodeDelegate is ynEigenIntegrationBaseTest { address operatorAddress = address(uint160(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))))); + // TODO: handle operatorAddress as payments receiver in PaymentsReceiver // register as operator vm.prank(operatorAddress); delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ - earningsReceiver: operatorAddress, + __deprecated_earningsReceiver: operatorAddress, // deprecated delegationApprover: address(0), stakerOptOutWindowBlocks: 1 }), @@ -153,11 +154,13 @@ contract TokenStakingNodeDelegate is ynEigenIntegrationBaseTest { address operatorAddress = address(uint160(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))))); + // TODO: handle operatorAddress as payments receiver in PaymentsReceiver + // Register as operator and delegate vm.prank(operatorAddress); delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ - earningsReceiver: operatorAddress, + __deprecated_earningsReceiver: operatorAddress, delegationApprover: address(0), stakerOptOutWindowBlocks: 1 }), diff --git a/test/integration/ynETH.t.sol b/test/integration/ynETH.t.sol index dc058811b..4cd3728f3 100644 --- a/test/integration/ynETH.t.sol +++ b/test/integration/ynETH.t.sol @@ -1,70 +1,338 @@ // SPDX-License-Identifier: BSD 3-Clause License pragma solidity ^0.8.24; -import {IntegrationBaseTest} from "test/integration/IntegrationBaseTest.sol"; -import {ynETH} from "src/ynETH.sol"; -import {ynBase} from "src/ynBase.sol"; -import {IStakingNode} from "src/interfaces/IStakingNode.sol"; import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; import {IEigenPod} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; +import {IStakingNode} from "../../src/interfaces/IStakingNode.sol"; + +import {ynETH} from "../../src/ynETH.sol"; +import {ynBase} from "../../src/ynBase.sol"; +import {WithdrawalQueueManager} from "../../src/WithdrawalQueueManager.sol"; + +import {IntegrationBaseTest, IStakingNodesManager, IRewardsDistributor} from "./IntegrationBaseTest.sol"; + import "forge-std/console.sol"; contract ynETHIntegrationTest is IntegrationBaseTest { - function testDepositETH() public { + error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); + error InvalidInitialization(); - emit log_named_uint("Block number at deposit test", block.number); + address public user = address(420); + address public receiver = address(69); - uint256 depositAmount = 1 ether; - vm.deal(address(this), depositAmount); - // Arrange - uint256 initialETHBalance = address(this).balance; + function testInitializeSetup() public { + assertEq(yneth.name(), "ynETH", "testInitialize: E0"); + assertEq(yneth.symbol(), "ynETH", "testInitialize: E1"); + assertEq(yneth.hasRole(yneth.DEFAULT_ADMIN_ROLE(), actors.admin.ADMIN), true, "testInitialize: E2"); + assertEq(yneth.hasRole(yneth.PAUSER_ROLE(), actors.ops.PAUSE_ADMIN), true, "testInitialize: E3"); + assertEq(yneth.hasRole(yneth.UNPAUSER_ROLE(), actors.admin.UNPAUSE_ADMIN), true, "testInitialize: E4"); + assertEq(address(yneth.stakingNodesManager()), address(stakingNodesManager), "testInitialize: E5"); + assertEq(address(yneth.rewardsDistributor()), address(rewardsDistributor), "testInitialize: E6"); + } - yneth.depositETH{value: depositAmount}(address(this)); + function testInitializeInvalidInitialization() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: actors.admin.ADMIN, + pauser: actors.ops.PAUSE_ADMIN, + unpauser: actors.admin.UNPAUSE_ADMIN, + stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)), + rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(InvalidInitialization.selector); + yneth.initialize(_init); + } - // Assert - uint256 finalETHBalance = address(this).balance; - uint256 ynETHBalance = yneth.balanceOf(address(this)); - uint256 expectedETHBalance = initialETHBalance - depositAmount; + function testInitializeAdminZeroAddress() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: address(0), + pauser: actors.ops.PAUSE_ADMIN, + unpauser: actors.admin.UNPAUSE_ADMIN, + stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)), + rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(ynETH.ZeroAddress.selector); + yneth.initialize(_init); + } - assertEq(finalETHBalance, expectedETHBalance, "ETH was not correctly deducted from sender"); - assertGt(ynETHBalance, 0, "ynETH balance should be greater than 0 after deposit"); + function testInitializePauserZeroAddress() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: actors.admin.ADMIN, + pauser: address(0), + unpauser: actors.admin.UNPAUSE_ADMIN, + stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)), + rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(ynETH.ZeroAddress.selector); + yneth.initialize(_init); } - function testDepositETHWhenPaused() public { - // Arrange + function testInitializeUnpauserZeroAddress() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: actors.admin.ADMIN, + pauser: actors.ops.PAUSE_ADMIN, + unpauser: address(0), + stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)), + rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(ynETH.ZeroAddress.selector); + yneth.initialize(_init); + } + + function testInitializeStakingNodesManagerZeroAddress() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: actors.admin.ADMIN, + pauser: actors.ops.PAUSE_ADMIN, + unpauser: actors.admin.UNPAUSE_ADMIN, + stakingNodesManager: IStakingNodesManager(address(0)), + rewardsDistributor: IRewardsDistributor(address(rewardsDistributor)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(ynETH.ZeroAddress.selector); + yneth.initialize(_init); + } + + function testInitializeRewardsDistributorZeroAddress() public { + address[] memory pauseWhitelist = new address[](1); + pauseWhitelist[0] = actors.eoa.DEFAULT_SIGNER; + ynETH.Init memory _init = ynETH.Init({ + admin: actors.admin.ADMIN, + pauser: actors.ops.PAUSE_ADMIN, + unpauser: actors.admin.UNPAUSE_ADMIN, + stakingNodesManager: IStakingNodesManager(address(stakingNodesManager)), + rewardsDistributor: IRewardsDistributor(address(0)), + pauseWhitelist: pauseWhitelist + }); + + vm.expectRevert(ynETH.ZeroAddress.selector); + yneth.initialize(_init); + } + + function testDepositETH(uint256 _amount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + + vm.deal(user, _amount); + + uint256 _userBalanceBefore = address(user).balance; + uint256 _recieverBalanceBefore = yneth.balanceOf(receiver); + uint256 _expectedAmountOut = yneth.previewDeposit(_amount); + uint256 _expectedAmountOut2 = yneth.convertToShares(_amount); + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalSupplyBefore = yneth.totalSupply(); + uint256 _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.prank(user); + uint256 _amountOut = yneth.depositETH{value: _amount}(receiver); + + assertEq(address(user).balance, _userBalanceBefore - _amount, "testDepositETH: E0"); + assertEq(yneth.balanceOf(receiver), _recieverBalanceBefore + _amount, "testDepositETH: E1"); + assertEq(_amountOut, _expectedAmountOut, "testDepositETH: E2"); + assertEq(yneth.totalAssets(), _totalAssetsBefore + _amount, "testDepositETH: E3"); + assertEq(yneth.totalSupply(), _totalSupplyBefore + _amountOut, "testDepositETH: E4"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore + _amount, "testDepositETH: E5"); + assertEq(_expectedAmountOut, _expectedAmountOut2, "testDepositETH: E6"); + } + + function testDepositETHWhenPaused(uint256 _amount) public { + vm.prank(actors.ops.PAUSE_ADMIN); yneth.pauseDeposits(); - uint256 depositAmount = 1 ether; - vm.deal(address(this), depositAmount); + vm.deal(user, _amount); - // Act & Assert vm.expectRevert(ynETH.Paused.selector); - yneth.depositETH{value: depositAmount}(address(this)); + vm.prank(user); + yneth.depositETH{value: _amount}(user); } - function testPauseDepositETH() public { - // Arrange + function testDepositETHZeroETH() public { + vm.expectRevert(ynETH.ZeroETH.selector); + yneth.depositETH{value: 0}(user); + } + + function testBurn(uint256 _amount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + + vm.deal(address(ynETHWithdrawalQueueManager), _amount); + vm.prank(address(ynETHWithdrawalQueueManager)); + yneth.depositETH{value: _amount}(address(ynETHWithdrawalQueueManager)); + + uint256 _userBalanceBefore = yneth.balanceOf(address(ynETHWithdrawalQueueManager)); + uint256 _totalSupplyBefore = yneth.totalSupply(); + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.prank(address(ynETHWithdrawalQueueManager)); + yneth.burn(_amount); + + assertEq(yneth.balanceOf(address(ynETHWithdrawalQueueManager)), _userBalanceBefore - _amount, "testBurn: E0"); + assertEq(yneth.totalSupply(), _totalSupplyBefore - _amount, "testBurn: E1"); + assertEq(yneth.totalAssets(), _totalAssetsBefore, "testBurn: E2"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore, "testBurn: E3"); + } + + function testBurnWrongCaller() public { + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), yneth.BURNER_ROLE())); + yneth.burn(1 ether); + } + + function testReceiveRewards(uint256 _depositAmount, uint256 _rewardAmount) public { + vm.assume(_depositAmount > 0 ether && _depositAmount <= 10_000 ether); + vm.assume(_rewardAmount > 0 ether && _rewardAmount <= 10_000 ether); + + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalSupplyBefore = yneth.totalSupply(); + uint256 _totalDepositedInPool = yneth.totalDepositedInPool(); + + vm.deal(user, _depositAmount); + vm.prank(user); + yneth.depositETH{value: _depositAmount}(user); + + assertEq(yneth.totalAssets(), _totalAssetsBefore + _depositAmount, "testReceiveRewards: E0"); + assertEq(yneth.totalSupply(), _totalSupplyBefore + _depositAmount, "testReceiveRewards: E1"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPool + _depositAmount, "testReceiveRewards: E2"); + + _totalAssetsBefore = yneth.totalAssets(); + _totalSupplyBefore = yneth.totalSupply(); + _totalDepositedInPool = yneth.totalDepositedInPool(); + + vm.deal(address(rewardsDistributor), _rewardAmount); + vm.prank(address(rewardsDistributor)); + yneth.receiveRewards{value: _rewardAmount}(); + + assertEq(yneth.totalAssets(), _totalAssetsBefore + _rewardAmount, "testReceiveRewards: E3"); + assertEq(yneth.totalSupply(), _totalSupplyBefore, "testReceiveRewards: E4"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPool + _rewardAmount, "testReceiveRewards: E5"); + } + + function testReceiveRewardsNotRewardsDistributor() public { + bytes memory encodedError = abi.encodeWithSelector(ynETH.NotRewardsDistributor.selector, address(this)); + vm.expectRevert(encodedError); + yneth.receiveRewards(); + } + + function testWithdrawETH(uint256 _amount) public { + vm.assume(_amount > 0 ether && _amount <= 10_000 ether); + + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.deal(user, _amount); + vm.prank(user); + yneth.depositETH{value: _amount}(user); + + assertEq(yneth.totalAssets(), _totalAssetsBefore + _amount, "testWithdrawETH: E0"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore + _amount, "testWithdrawETH: E1"); + + _totalAssetsBefore = yneth.totalAssets(); + _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.prank(address(stakingNodesManager)); + yneth.withdrawETH(_amount); + + assertEq(yneth.totalAssets(), _totalAssetsBefore - _amount, "testWithdrawETH: E2"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore - _amount, "testWithdrawETH: E3"); + } + + function testWithdrawETHWrongCaller() public { + address arbitraryCaller = address(0x456); + vm.deal(arbitraryCaller, 100 ether); + vm.prank(arbitraryCaller); + vm.expectRevert(abi.encodeWithSelector(ynETH.CallerNotStakingNodeManager.selector, address(stakingNodesManager), arbitraryCaller)); + yneth.withdrawETH(1 ether); + } + + function testWithdrawETHInsufficientBalance() public { + vm.expectRevert(abi.encodeWithSelector(ynETH.InsufficientBalance.selector)); + vm.prank(address(stakingNodesManager)); + yneth.withdrawETH(1); + } + + function testProcessWithdrawnETH(uint256 _amount) public { + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.deal(address(stakingNodesManager), _amount); + vm.prank(address(stakingNodesManager)); + yneth.processWithdrawnETH{value: _amount}(); + + assertEq(yneth.totalAssets(), _totalAssetsBefore + _amount, "testProcessWithdrawnETH: E0"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore + _amount, "testProcessWithdrawnETH: E1"); + } + + function testProcessWithdrawnETHVault(uint256 _amount) public { + uint256 _totalAssetsBefore = yneth.totalAssets(); + uint256 _totalDepositedInPoolBefore = yneth.totalDepositedInPool(); + + vm.deal(address(stakingNodesManager.redemptionAssetsVault()), _amount); + vm.prank(address(stakingNodesManager.redemptionAssetsVault())); + yneth.processWithdrawnETH{value: _amount}(); + + assertEq(yneth.totalAssets(), _totalAssetsBefore + _amount, "testProcessWithdrawnETH: E0"); + assertEq(yneth.totalDepositedInPool(), _totalDepositedInPoolBefore + _amount, "testProcessWithdrawnETH: E1"); + } + + function testProcessWithdrawnETHWrongCaller() public { + address arbitraryCaller = address(0x456); + vm.deal(arbitraryCaller, 100 ether); + vm.prank(arbitraryCaller); + vm.expectRevert(abi.encodeWithSelector(ynETH.CallerNotAuthorized.selector, arbitraryCaller)); + yneth.processWithdrawnETH{value: 1 ether}(); + } + + function testPauseDeposits() public { vm.prank(actors.ops.PAUSE_ADMIN); yneth.pauseDeposits(); + assertTrue(yneth.depositsPaused(), "testPauseDepositETH: E0"); - // Act & Assert - bool pauseState = yneth.depositsPaused(); - assertTrue(pauseState, "Deposit ETH should be paused"); + vm.deal(user, 1 ether); + vm.expectRevert(ynETH.Paused.selector); + vm.prank(user); + yneth.depositETH{value: 1 ether}(user); } - function testUnpauseDepositETH() public { - // Arrange - vm.prank(actors.ops.PAUSE_ADMIN); + function testPauseDepositsWrongCaller() public { + address arbitraryCaller = address(0x456); + vm.prank(arbitraryCaller); + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), yneth.PAUSER_ROLE())); yneth.pauseDeposits(); + } + + function testUnpauseDeposits() public { + + testPauseDeposits(); + vm.prank(actors.admin.UNPAUSE_ADMIN); yneth.unpauseDeposits(); - // Act & Assert - bool pauseState = yneth.depositsPaused(); - assertFalse(pauseState, "Deposit ETH should be unpaused"); + assertFalse(yneth.depositsPaused(), "testUnpauseDeposits: E0"); + } + + function testUnpauseDepositsWrongCaller() public { + address arbitraryCaller = address(0x456); + vm.prank(arbitraryCaller); + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), yneth.UNPAUSER_ROLE())); + yneth.unpauseDeposits(); } function testPreviewDeposit() public { @@ -214,33 +482,6 @@ contract ynETHIntegrationTest is IntegrationBaseTest { assertEq(finalFeeReceiverBalance, initialFeeReceiverBalance + expectedFees, "Incorrect feeReceiver balance after rewards distribution"); } - function testPauseDepositETHFunctionality() public { - // Arrange - vm.prank(actors.ops.PAUSE_ADMIN); - yneth.pauseDeposits(); - - // Act & Assert - bool pauseState = yneth.depositsPaused(); - assertTrue(pauseState, "Deposit ETH should be paused after setting pause state to true"); - - // Trying to deposit ETH while paused - uint256 depositAmount = 1 ether; - vm.expectRevert(ynETH.Paused.selector); - yneth.depositETH{value: depositAmount}(address(this)); - - // Unpause and try depositing again - vm.prank(actors.admin.UNPAUSE_ADMIN); - yneth.unpauseDeposits(); - pauseState = yneth.depositsPaused(); - - assertFalse(pauseState, "Deposit ETH should be unpaused after setting pause state to false"); - - // Deposit should succeed now - yneth.depositETH{value: depositAmount}(address(this)); - uint256 ynETHBalance = yneth.balanceOf(address(this)); - assertGt(ynETHBalance, 0, "ynETH balance should be greater than 0 after deposit"); - } - function testTransferFailsForNonWhitelistedAddresses() public { // Arrange uint256 transferAmount = 1 ether; @@ -348,19 +589,49 @@ contract ynETHIntegrationTest is IntegrationBaseTest { yneth.depositETH{value: 0}(address(this)); } - function testReceiveRewardsWithBadRewardsDistributor() public { - bytes memory encodedError = abi.encodeWithSelector(ynETH.NotRewardsDistributor.selector); - vm.expectRevert(encodedError); - yneth.receiveRewards(); + function testPreviewRedeemAfterDepositAndRewards(uint256 _amount, uint256 _rewardAmount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + vm.assume(_rewardAmount > 0 && _rewardAmount <= 10_000 ether); + + testDepositETH(_amount); + + uint256 _userBalance = yneth.balanceOf(receiver); + assertTrue(_userBalance > 0, "testPreviewRedeemAfterDepositAndRewards: E0"); // sanity check + uint256 _previewRedeemBefore = yneth.previewRedeem(yneth.balanceOf(receiver)); + assertEq(_previewRedeemBefore, _amount, "testPreviewRedeemAfterDepositAndRewards: E1"); + + vm.deal(address(executionLayerReceiver), _rewardAmount); + rewardsDistributor.processRewards(); + + assertApproxEqAbs(yneth.previewRedeem(_userBalance), _previewRedeemBefore + (_rewardAmount * 9_000 / 10_000), 10, "testPreviewRedeemAfterDepositAndRewards: E2"); } - function testWithdrawETHWithZeroBalance() public { - bytes memory encodedError = abi.encodeWithSelector(ynETH.InsufficientBalance.selector); - vm.startPrank(address(stakingNodesManager)); - vm.expectRevert(encodedError); - yneth.withdrawETH(1); - vm.stopPrank(); - } + function testConvertToAssetsAfterDepositAndRewards(uint256 _amount, uint256 _rewardAmount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + vm.assume(_rewardAmount > 0 && _rewardAmount <= 10_000 ether); + + testDepositETH(_amount); + + uint256 _userBalance = yneth.balanceOf(receiver); + assertTrue(_userBalance > 0, "testConvertToAssetsAfterDepositAndRewards: E0"); + uint256 _previewRedeemBefore = yneth.convertToAssets(yneth.balanceOf(receiver)); + assertEq(_previewRedeemBefore, _amount, "testConvertToAssetsAfterDepositAndRewards: E1"); + + vm.deal(address(executionLayerReceiver), _rewardAmount); + rewardsDistributor.processRewards(); + + assertApproxEqAbs(yneth.convertToAssets(_userBalance), _previewRedeemBefore + (_rewardAmount * 9_000 / 10_000), 10, "testConvertToAssetsAfterDepositAndRewards: E2"); + } + + function testPreviewRedeemBeforeDeposit(uint256 _amount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + assertEq(yneth.previewRedeem(_amount), _amount, "testPreviewRedeemBeforeDeposit: E0"); + } + + function testConvertToAssetsBeforeDeposit(uint256 _amount) public { + vm.assume(_amount > 0 && _amount <= 10_000 ether); + assertEq(yneth.convertToAssets(_amount), _amount, "testConvertToAssetsBeforeDeposit: E0"); + } } @@ -382,23 +653,6 @@ contract ynETHTotalAssetsTest is IntegrationBaseTest { assertEq(totalAssetsAfterSecondDeposit, initialTotalAssets + depositAmount1 + depositAmount2, "Total assets should increase by the sum of both deposit amounts"); } - function testFuzzTotalAssetsWithRewards(uint256 depositAmount, uint256 rewardAmount) public { - // Arrange - vm.assume(depositAmount > 0 ether && depositAmount <= 10000 ether); - vm.assume(rewardAmount > 0 ether && rewardAmount <= 10000 ether); - yneth.depositETH{value: depositAmount}(address(this)); - uint256 totalAssetsAfterDeposit = yneth.totalAssets(); - - // Act - vm.deal(address(rewardsDistributor), rewardAmount); - vm.startPrank(address(rewardsDistributor)); - yneth.receiveRewards{value: rewardAmount}(); - uint256 totalAssetsAfterRewards = yneth.totalAssets(); - - // Assert - assertEq(totalAssetsAfterRewards, totalAssetsAfterDeposit + rewardAmount, "Total assets should increase by the reward amount"); - } - function skiptestFuzzTotalAssetsWithRewardsInEigenPods(uint256 depositAmount, uint256 rewardAmount, uint256 stakingNodeCount) public { // Arrange vm.assume(depositAmount > 0 ether && depositAmount <= 10000 ether); diff --git a/test/mocks/DeployMockAggregator.s.sol b/test/mocks/DeployMockAggregator.s.sol deleted file mode 100644 index c7796f71c..000000000 --- a/test/mocks/DeployMockAggregator.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Script.sol"; -import "test/mocks/MockAggregatorV3.sol"; - -contract DeployMockAggregator is Script { - function run() external { - vm.startBroadcast(); - new MockAggregatorV3(); - vm.stopBroadcast(); - } -} - -// holesky: 0x8d543b63C5bBAAaAbb9032773bd85F8EefC4c92a diff --git a/test/mocks/MockAggregatorV3.sol b/test/mocks/MockAggregatorV3.sol deleted file mode 100644 index d180d3323..000000000 --- a/test/mocks/MockAggregatorV3.sol +++ /dev/null @@ -1,61 +0,0 @@ -/// SPDX-License-Identifier: BSD 3-Clause License - -pragma solidity ^0.8.24; - -import {AggregatorV3Interface} from "src/external/chainlink/AggregatorV3Interface.sol"; - -contract MockAggregatorV3 is AggregatorV3Interface { - - // implement a mock aggregator - function decimals() external pure override returns (uint8) { - return 18; - } - - function description() external pure override returns (string memory) { - return "Mock Aggregator"; - } - - function version() external pure override returns (uint256) { - return 1; - } - - function getRoundData( - uint80 _roundId - ) - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - // Implement your mock logic here - roundId = _roundId; - answer = 1.01 ether; - startedAt = block.timestamp - 3600; - updatedAt = block.timestamp - 1800; - answeredInRound = 1; - } - - function latestRoundData() - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - // Implement your mock logic here - roundId = 1; - answer = 1.01 ether; - startedAt = block.timestamp - 3600; - updatedAt = block.timestamp - 1800; - answeredInRound = 1; - } -} \ No newline at end of file diff --git a/test/mocks/MockEigenLayerBeaconOracle.sol b/test/mocks/MockEigenLayerBeaconOracle.sol deleted file mode 100644 index 16bdba70f..000000000 --- a/test/mocks/MockEigenLayerBeaconOracle.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; - -import "lib/eigenlayer-contracts/src/contracts/interfaces/IBeaconChainOracle.sol"; - -contract MockEigenLayerBeaconOracle is IBeaconChainOracle { - bytes32 public mockBeaconChainStateRoot; - - function getOracleBlockRootAtTimestamp() external view returns(bytes32) { - return mockBeaconChainStateRoot; - } - - function setOracleBlockRootAtTimestamp(bytes32 beaconChainStateRoot) external { - mockBeaconChainStateRoot = beaconChainStateRoot; - } - - function timestampToBlockRoot(uint256 /*blockNumber*/) external view returns(bytes32) { - return mockBeaconChainStateRoot; - } -} diff --git a/test/mocks/MockEigenPod.sol b/test/mocks/MockEigenPod.sol deleted file mode 100644 index 77bb86fe9..000000000 --- a/test/mocks/MockEigenPod.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; - -import "lib/eigenlayer-contracts/src/contracts/interfaces/IETHPOSDeposit.sol"; -import "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; -import "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; -import "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; -import "lib/eigenlayer-contracts/src/contracts/interfaces/IPausable.sol"; -import { EigenPod } from "lib/eigenlayer-contracts/src/contracts/pods/EigenPod.sol"; - -contract MockEigenPod is EigenPod { - constructor( - IETHPOSDeposit _ethPOS, - IDelayedWithdrawalRouter _delayedWithdrawalRouter, - IEigenPodManager _eigenPodManager, - uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - uint64 _GENESIS_TIME - ) EigenPod(_ethPOS, _delayedWithdrawalRouter, _eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, _GENESIS_TIME) {} - - function setPodOwner(address newOwner) external { - require(newOwner != address(0), "MockEigenPod: New owner is the zero address"); - podOwner = newOwner; - } - - function setValidatorInfo( - bytes32 validatorPubkeyHash, - ValidatorInfo memory validatorInfo - ) external { - _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo; - } -} diff --git a/test/mocks/MockEigenPodManager.sol b/test/mocks/MockEigenPodManager.sol deleted file mode 100644 index 8f3b4f967..000000000 --- a/test/mocks/MockEigenPodManager.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; - -import { IEigenPod } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; -import { IETHPOSDeposit } from "lib/eigenlayer-contracts/src/contracts/interfaces/IETHPOSDeposit.sol"; -import { IStrategyManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; -import { ISlasher } from "lib/eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol"; -import { IDelegationManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; -import { EigenPodManager } from "lib/eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol"; -//import { IBeacon } from "lib/eigenlayer-contracts/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; -import { IBeacon } from "lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; - -contract MockEigenPodManager is EigenPodManager { - - constructor(EigenPodManager _eigenPodManagerInstance) - EigenPodManager( - _eigenPodManagerInstance.ethPOS(), - _eigenPodManagerInstance.eigenPodBeacon(), - _eigenPodManagerInstance.strategyManager(), - _eigenPodManagerInstance.slasher(), - _eigenPodManagerInstance.delegationManager() - ) {} - // Function to manually set the hasPod status for a given owner in the mock - function setHasPod(address podOwner, IEigenPod pod) external { - ownerToPod[podOwner] = pod; - } -} diff --git a/test/mocks/MockStakingNode.sol b/test/mocks/MockStakingNode.sol deleted file mode 100644 index 041d6f8e0..000000000 --- a/test/mocks/MockStakingNode.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; - -import "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; -import {StakingNode} from "src/StakingNode.sol"; - -contract MockStakingNode is StakingNode { - function setEigenPod(IEigenPod _eigenPod) public { - if (address(_eigenPod) == address(0)) revert ZeroAddress(); - eigenPod = _eigenPod; - emit EigenPodCreated(address(this), address(_eigenPod)); - } -} diff --git a/test/mocks/MockYnETHERC4626.sol b/test/mocks/MockYnETHERC4626.sol index 8d1acf92a..886159ef0 100644 --- a/test/mocks/MockYnETHERC4626.sol +++ b/test/mocks/MockYnETHERC4626.sol @@ -102,11 +102,42 @@ contract MockYnETHERC4626 is IynETH, AccessControlUpgradeable, ERC4626Upgradeabl /// @notice Calculates the amount of shares to be minted for a given deposit. /// @param assets The amount of assets to be deposited. /// @return The amount of shares to be minted. - function previewDeposit(uint256 assets) public view override returns (uint256) { + function previewDeposit(uint256 assets) public view override(ERC4626Upgradeable, IynETH) returns (uint256) { return _convertToShares(assets, Math.Rounding.Floor); } - function totalAssets() public view override returns (uint256) { + //-------------------------------------------------------------------------------------- + //---------------------------------- WITHDRAWALS -------------------------------------- + //-------------------------------------------------------------------------------------- + + function previewRedeem(uint256 shares) public view override(ERC4626Upgradeable, IynETH) returns (uint256 assets) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal override view returns (uint256) { + + uint256 supply = totalSupply(); + + // 1:1 exchange rate on the first stake. + // Use totalSupply to see if this call is made before boostrap call, not totalAssets + if (supply == 0) { + return shares; + } + return Math.mulDiv(shares, totalAssets(), supply, rounding); + } + + /// @notice Burns a specified amount of ynETH shares. + /// @param amount The amount of shares to burn. + function burn(uint256 amount) external override { + _burn(msg.sender, amount); + } + + + //-------------------------------------------------------------------------------------- + //---------------------------------- ASSETS ------------------------------------------- + //-------------------------------------------------------------------------------------- + + function totalAssets() public view override(ERC4626Upgradeable, IynETH) returns (uint256) { uint256 total = 0; // allocated ETH for deposits pending to be processed total += totalDepositedInPool; diff --git a/test/mocks/TestStakingNodeV2.sol b/test/mocks/TestStakingNodeV2.sol index 23760dad8..363948d53 100644 --- a/test/mocks/TestStakingNodeV2.sol +++ b/test/mocks/TestStakingNodeV2.sol @@ -15,7 +15,7 @@ contract TestStakingNodeV2 is StakingNode { uint valueToBeInitialized; } - function initializeV2(ReInit memory reInit) public reinitializer(2) { + function initializeV2(ReInit memory reInit) public reinitializer(3) { valueToBeInitialized = reInit.valueToBeInitialized; } diff --git a/test/mocks/TestStakingNodesManagerV2.sol b/test/mocks/TestStakingNodesManagerV2.sol index b0a98768a..837c4c22a 100644 --- a/test/mocks/TestStakingNodesManagerV2.sol +++ b/test/mocks/TestStakingNodesManagerV2.sol @@ -11,7 +11,7 @@ contract TestStakingNodesManagerV2 is StakingNodesManager { uint256 newV2Value; } - function initializeV2(ReInit memory reInit) public reinitializer(2) { + function initializeV3(ReInit memory reInit) public reinitializer(3) { newV2Value = reInit.newV2Value; } @@ -25,7 +25,11 @@ contract TestStakingNodesManagerV2 is StakingNodesManager { initializedVersion = node.getInitializedVersion(); } - if (initializedVersion == 1) { + if (initializedVersion == 1) { + node.initializeV2(0); + } + + if (initializedVersion == 2) { TestStakingNodeV2(payable(address(node))) .initializeV2(TestStakingNodeV2.ReInit({valueToBeInitialized: 23})); } diff --git a/test/scenarios/DepositPool-Scenario.spec.sol b/test/scenarios/DepositPool-Scenario.spec.sol index 3f5cad322..204e9aaae 100644 --- a/test/scenarios/DepositPool-Scenario.spec.sol +++ b/test/scenarios/DepositPool-Scenario.spec.sol @@ -6,6 +6,8 @@ import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/ import {PooledDepositsVault} from "src/PooledDepositsVault.sol"; import {IynETH} from "src/interfaces/IynETH.sol"; import {Test} from"forge-std/Test.sol"; +import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; + contract MockynETH is IynETH { // Implement necessary mock functions @@ -25,6 +27,38 @@ contract MockynETH is IynETH { function processWithdrawnETH() external payable {} function approve(address spender, uint256 value) external returns (bool) {} function allowance(address owner, address spender) external view returns (uint256) {} + function previewRedeem(uint256 shares) external view returns (uint256 assets) {} + + function totalAssets() external view returns (uint256) { + // Mock implementation returning a fixed value + return 0 ether; + } + + function stakingNodesManager() external view returns (IStakingNodesManager) { + // Mock implementation returning a zero address + return IStakingNodesManager(address(0)); + } + + function previewDeposit(uint256 assets) external view returns (uint256 shares) { + // Mock implementation with a 1:1 ratio + return assets; + } + + function burn(uint256 amount) external override { + // Empty implementation for burn function + } + + function decimals() external pure override returns (uint8) { + return 18; // Assuming a common decimal value + } + + function name() external pure override returns (string memory) { + return "Mock ynETH"; // Example token name + } + + function symbol() external pure override returns (string memory) { + return "mynETH"; // Example token symbol + } } contract PooledDepositsScenarioTest is IntegrationBaseTest { diff --git a/test/scenarios/ScenarioBaseTest.sol b/test/scenarios/ScenarioBaseTest.sol index 7c9c44fb6..c50e12cb3 100644 --- a/test/scenarios/ScenarioBaseTest.sol +++ b/test/scenarios/ScenarioBaseTest.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IStrategyManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; -import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; +// import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import {IDepositContract} from "src/external/ethereum/IDepositContract.sol"; import {IEigenPodManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; @@ -25,6 +25,16 @@ import {StakingNode} from "src/StakingNode.sol"; import {Utils} from "script/Utils.sol"; import {ActorAddresses} from "script/Actors.sol"; import {TestAssetUtils} from "test/utils/TestAssetUtils.sol"; +import {ITransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {WithdrawalQueueManager} from "src/WithdrawalQueueManager.sol"; +import {WithdrawalsProcessor} from "src/WithdrawalsProcessor.sol"; + +import { ynETHRedemptionAssetsVault } from "src/ynETHRedemptionAssetsVault.sol"; +import {IRedeemableAsset} from "src/interfaces/IRedeemableAsset.sol"; +import {IRedemptionAssetsVault} from "src/interfaces/IRedemptionAssetsVault.sol"; +import "forge-std/console.sol"; + contract ScenarioBaseTest is Test, Utils { @@ -46,10 +56,15 @@ contract ScenarioBaseTest is Test, Utils { // Assets ynETH public yneth; + // Withdrawals + WithdrawalQueueManager public ynETHWithdrawalQueueManager; + ynETHRedemptionAssetsVault public ynETHRedemptionAssetsVaultInstance; + WithdrawalsProcessor public withdrawalsProcessor; + // Eigen IEigenPodManager public eigenPodManager; IDelegationManager public delegationManager; - IDelayedWithdrawalRouter public delayedWithdrawalRouter; + // IDelayedWithdrawalRouter public delayedWithdrawalRouter; IStrategyManager public strategyManager; // Ethereum @@ -73,7 +88,7 @@ contract ScenarioBaseTest is Test, Utils { // Assign Eigenlayer addresses eigenPodManager = IEigenPodManager(chainAddresses.eigenlayer.EIGENPOD_MANAGER_ADDRESS); delegationManager = IDelegationManager(chainAddresses.eigenlayer.DELEGATION_MANAGER_ADDRESS); - delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); + // delayedWithdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); strategyManager = IStrategyManager(chainAddresses.eigenlayer.STRATEGY_MANAGER_ADDRESS); // Assign LSD addresses @@ -85,5 +100,8 @@ contract ScenarioBaseTest is Test, Utils { rewardsDistributor = RewardsDistributor(payable(chainAddresses.yn.REWARDS_DISTRIBUTOR_ADDRESS)); executionLayerReceiver = RewardsReceiver(payable(chainAddresses.yn.EXECUTION_LAYER_RECEIVER_ADDRESS)); consensusLayerReceiver = RewardsReceiver(payable(chainAddresses.yn.CONSENSUS_LAYER_RECEIVER_ADDRESS)); + ynETHWithdrawalQueueManager = WithdrawalQueueManager(payable(chainAddresses.yn.WITHDRAWAL_QUEUE_MANAGER_ADDRESS)); + ynETHRedemptionAssetsVaultInstance = ynETHRedemptionAssetsVault(payable(chainAddresses.yn.YNETH_REDEMPTION_ASSETS_VAULT_ADDRESS)); + withdrawalsProcessor = WithdrawalsProcessor(payable(chainAddresses.yn.WITHDRAWALS_PROCESSOR_ADDRESS)); } } \ No newline at end of file diff --git a/test/scenarios/fork/ProtocolUpgrade-Scenario.spec.sol b/test/scenarios/fork/ProtocolUpgrade-Scenario.spec.sol index e7f92a362..6fcf46a21 100644 --- a/test/scenarios/fork/ProtocolUpgrade-Scenario.spec.sol +++ b/test/scenarios/fork/ProtocolUpgrade-Scenario.spec.sol @@ -19,9 +19,18 @@ import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beac import {TestStakingNodesManagerV2} from "test/mocks/TestStakingNodesManagerV2.sol"; import {TestStakingNodeV2} from "test/mocks/TestStakingNodeV2.sol"; +import {WithdrawalQueueManager} from "src/WithdrawalQueueManager.sol"; +import {WithdrawalsProcessor} from "src/WithdrawalsProcessor.sol"; +import {ynETHRedemptionAssetsVault} from "src/ynETHRedemptionAssetsVault.sol"; + contract ProtocolUpgradeScenario is ScenarioBaseTest { - address YNSecurityCouncil = 0xfcad670592a3b24869C0b51a6c6FDED4F95D6975; + address YNSecurityCouncil; + + function setUp() public override { + super.setUp(); + YNSecurityCouncil = actors.wallets.YNSecurityCouncil; + } function test_Upgrade_ynETH_Scenario() public { address previousImplementation = getTransparentUpgradeableProxyImplementationAddress(address(yneth)); @@ -98,6 +107,51 @@ contract ProtocolUpgradeScenario is ScenarioBaseTest { runSystemStateInvariants(previousTotalDeposited, previousTotalAssets, previousTotalSupply); } + function test_Upgrade_WithdrawalQueueManager_Scenario() public { + address previousWithdrawalQueueManagerImpl = getTransparentUpgradeableProxyImplementationAddress(address(ynETHWithdrawalQueueManager)); + address newWithdrawalQueueManagerImpl = address(new WithdrawalQueueManager()); + + uint256 previousTotalDeposited = yneth.totalDepositedInPool(); + uint256 previousTotalAssets = yneth.totalAssets(); + uint256 previousTotalSupply = IERC20(address(yneth)).totalSupply(); + + vm.prank(YNSecurityCouncil); + ProxyAdmin(getTransparentUpgradeableProxyAdminAddress(address(ynETHWithdrawalQueueManager))).upgradeAndCall(ITransparentUpgradeableProxy(address(ynETHWithdrawalQueueManager)), newWithdrawalQueueManagerImpl, ""); + + runUpgradeInvariants(address(ynETHWithdrawalQueueManager), previousWithdrawalQueueManagerImpl, newWithdrawalQueueManagerImpl); + runSystemStateInvariants(previousTotalDeposited, previousTotalAssets, previousTotalSupply); + } + + function test_Upgrade_WithdrawalsProcessor_Scenario() public { + address previousWithdrawalsProcessorImpl = getTransparentUpgradeableProxyImplementationAddress(address(withdrawalsProcessor)); + address newWithdrawalsProcessorImpl = address(new WithdrawalsProcessor()); + + uint256 previousTotalDeposited = yneth.totalDepositedInPool(); + uint256 previousTotalAssets = yneth.totalAssets(); + uint256 previousTotalSupply = IERC20(address(yneth)).totalSupply(); + + vm.prank(YNSecurityCouncil); + ProxyAdmin(getTransparentUpgradeableProxyAdminAddress(address(withdrawalsProcessor))).upgradeAndCall(ITransparentUpgradeableProxy(address(withdrawalsProcessor)), newWithdrawalsProcessorImpl, ""); + + runUpgradeInvariants(address(withdrawalsProcessor), previousWithdrawalsProcessorImpl, newWithdrawalsProcessorImpl); + runSystemStateInvariants(previousTotalDeposited, previousTotalAssets, previousTotalSupply); + } + + function test_Upgrade_YnETHRedemptionAssetsVault_Scenario() public { + address previousYnETHRedemptionAssetsVaultImpl = getTransparentUpgradeableProxyImplementationAddress(address(ynETHRedemptionAssetsVaultInstance)); + address newYnETHRedemptionAssetsVaultImpl = address(new ynETHRedemptionAssetsVault()); + + uint256 previousTotalDeposited = yneth.totalDepositedInPool(); + uint256 previousTotalAssets = yneth.totalAssets(); + uint256 previousTotalSupply = IERC20(address(yneth)).totalSupply(); + + vm.prank(YNSecurityCouncil); + ProxyAdmin(getTransparentUpgradeableProxyAdminAddress(address(ynETHRedemptionAssetsVaultInstance))).upgradeAndCall(ITransparentUpgradeableProxy(address(ynETHRedemptionAssetsVaultInstance)), newYnETHRedemptionAssetsVaultImpl, ""); + + runUpgradeInvariants(address(ynETHRedemptionAssetsVaultInstance), previousYnETHRedemptionAssetsVaultImpl, newYnETHRedemptionAssetsVaultImpl); + runSystemStateInvariants(previousTotalDeposited, previousTotalAssets, previousTotalSupply); + } + function test_Upgrade_StakingNodeImplementation_Scenario() public { // Collect all existing eigenPod addresses before the upgrade IStakingNode[] memory stakingNodes = stakingNodesManager.getAllNodes(); diff --git a/test/scenarios/fork/UnpauseYnETHTransfers-Scenario.spec.sol b/test/scenarios/fork/UnpauseYnETHTransfers-Scenario.spec.sol deleted file mode 100644 index 8d7cb59fc..000000000 --- a/test/scenarios/fork/UnpauseYnETHTransfers-Scenario.spec.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: BSD 3-Clause License -pragma solidity ^0.8.24; -import {StakingNodesManager} from "src/StakingNodesManager.sol"; -import {ynETH} from "src/ynETH.sol"; -import {RewardsReceiver} from "src/RewardsReceiver.sol"; -import {RewardsDistributor} from "src/RewardsDistributor.sol"; -import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; -import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; -import {IStakingNode} from "src/interfaces/IStakingNodesManager.sol"; -import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {ITransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {ScenarioBaseTest} from "test/scenarios/ScenarioBaseTest.sol"; -import { Invariants } from "test/scenarios/Invariants.sol"; - -import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import {TestStakingNodesManagerV2} from "test/mocks/TestStakingNodesManagerV2.sol"; -import {TestStakingNodeV2} from "test/mocks/TestStakingNodeV2.sol"; - -contract UnpauseYnETHScenario is ScenarioBaseTest { - - address YNSecurityCouncil = 0xfcad670592a3b24869C0b51a6c6FDED4F95D6975; - - - function setUp() public override { - super.setUp(); - - // All tests start with unpausing - uint256 previousTotalDeposited = yneth.totalDepositedInPool(); - uint256 previousTotalAssets = yneth.totalAssets(); - uint256 previousTotalSupply = IERC20(address(yneth)).totalSupply(); - - vm.prank(YNSecurityCouncil); - yneth.unpauseTransfers(); - - runSystemStateInvariants(previousTotalDeposited, previousTotalAssets, previousTotalSupply); - } - - function test_unpauseTransfers_ynETH_NewDeposit_and_Transfer_Scenario() public { - - // Simulate a deposit of ETH and transfer of ynETH tokens - address receiver = address(0x123); - address to = address(0x456); - uint256 depositAmount = 1000 ether; - uint256 transferAmount = 100 ether; // 100 ynETH - - // Deposit ETH and receive ynETH shares - vm.deal(receiver, depositAmount); // Provide ETH to the receiver - vm.prank(receiver); - yneth.depositETH{value: depositAmount}(receiver); - - // Transfer ynETH shares to another address - vm.prank(receiver); - yneth.transfer(to, transferAmount); - - assertEq(yneth.balanceOf(to), transferAmount, "Transfer balance check failed"); - } - - function test_unpauseTransfers_transferFromWhale_Scenario() public { - address whale = address(0xB9779AeC32f4cbF376F325d8c393B0D2711874eD); - address recipient = address(0xDeaDBEEFCAfebABe000000000000000000000000); - uint256 whaleBalance = yneth.balanceOf(whale); - uint256 transferAmount = whaleBalance / 2; - - vm.prank(whale); - yneth.transfer(recipient, transferAmount); - - assertEq(yneth.balanceOf(recipient), transferAmount, "Recipient did not receive the correct amount of ynETH"); - } - - function test_unpauseTransfers_tryToUnpauseAgain_Scenario() public { - vm.prank(YNSecurityCouncil); - yneth.unpauseTransfers(); - - address whale = address(0xB9779AeC32f4cbF376F325d8c393B0D2711874eD); - address recipient = address(0xDeaDBEEFCAfebABe000000000000000000000000); - uint256 whaleBalance = yneth.balanceOf(whale); - uint256 transferAmount = whaleBalance / 2; - - vm.prank(whale); - yneth.transfer(recipient, transferAmount); - - assertEq(yneth.balanceOf(recipient), transferAmount, "Recipient did not receive the correct amount of ynETH"); - } - - function runSystemStateInvariants( - uint256 previousTotalDeposited, - uint256 previousTotalAssets, - uint256 previousTotalSupply - ) public { - assertEq(yneth.totalDepositedInPool(), previousTotalDeposited, "Total deposit integrity check failed"); - assertEq(yneth.totalAssets(), previousTotalAssets, "Total assets integrity check failed"); - assertEq(yneth.totalSupply(), previousTotalSupply, "Share mint integrity check failed"); - } -} \ No newline at end of file diff --git a/test/scenarios/fork/ynETHUserWithdrawal-Scenario.spec.sol b/test/scenarios/fork/ynETHUserWithdrawal-Scenario.spec.sol new file mode 100644 index 000000000..caa47e58f --- /dev/null +++ b/test/scenarios/fork/ynETHUserWithdrawal-Scenario.spec.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; +import {StakingNodesManager} from "src/StakingNodesManager.sol"; +import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import {ynETH} from "src/ynETH.sol"; +import {RewardsReceiver} from "src/RewardsReceiver.sol"; +import {RewardsDistributor} from "src/RewardsDistributor.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; +import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; +import {IStakingNode} from "src/interfaces/IStakingNodesManager.sol"; +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ITransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {ScenarioBaseTest} from "test/scenarios/ScenarioBaseTest.sol"; +import { Invariants } from "test/scenarios/Invariants.sol"; +import {stdStorage, StdStorage} from "forge-std/Test.sol"; +import {BytesLib} from "lib/eigenlayer-contracts/src/contracts/libraries/BytesLib.sol"; + +import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import {TestStakingNodesManagerV2} from "test/mocks/TestStakingNodesManagerV2.sol"; +import {TestStakingNodeV2} from "test/mocks/TestStakingNodeV2.sol"; + +import {BeaconChainProofs} from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; +import {Merkle} from "lib/eigenlayer-contracts/src/contracts/libraries/Merkle.sol"; +import { ProofParsingV1 } from "test/eigenlayer-utils/ProofParsingV1.sol"; +import {Utils} from "script/Utils.sol"; +import {beaconChainETHStrategy} from "src/Constants.sol"; +import { StakingNodeTestBase } from "test/utils/StakingNodeTestBase.sol"; +import {Vm} from "lib/forge-std/src/Vm.sol"; +import {IWithdrawalQueueManager} from "src/interfaces/IWithdrawalQueueManager.sol"; + + +import "forge-std/console.sol"; + + +contract ynETHUserWithdrawalScenarioOnHolesky is StakingNodeTestBase { + using stdStorage for StdStorage; + using BytesLib for bytes; + + + struct TestState { + uint256 nodeId; + uint256 withdrawalAmount; + IStakingNode stakingNodeInstance; + uint256 totalAssetsBefore; + uint256 totalSupplyBefore; + uint256[] stakingNodeBalancesBefore; + uint256 previousYnETHRedemptionAssetsVaultBalance; + uint256 previousYnETHBalance; + } + + function test_UserWithdrawal_1ETH_Holesky() public { + + if (block.chainid != 17000) { + return; // Skip test if not on Holesky + } + + // Withdrawing user configuration + uint256 userRequestedAmountYnETH = 1 ether; + address userAddress = address(0x12345678); + address receivalAddress = address(0x987654321); + vm.deal(userAddress, 100 ether); // Give the user some Ether to start with + vm.prank(userAddress); + yneth.depositETH{value: 10 ether}(userAddress); // User mints ynETH by depositing ETH + + TestState memory state = TestState({ + nodeId: 2, + withdrawalAmount: 32 ether, + stakingNodeInstance: stakingNodesManager.nodes(2), + totalAssetsBefore: yneth.totalAssets(), + totalSupplyBefore: yneth.totalSupply(), + stakingNodeBalancesBefore: getAllStakingNodeBalances(), + previousYnETHRedemptionAssetsVaultBalance: ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(), + previousYnETHBalance: address(yneth).balance + }); + + + + // This test handles the following Validator: + // 1692468 + // 0xa5d87f6440fbac9a0f40f192f618e24512572c5b54dbdb51960772ea9b3e9dc985a5703f2e837da9bc08c28e4f633984 + // https://holesky.beaconcha.in/validator/a5d87f6440fbac9a0f40f192f618e24512572c5b54dbdb51960772ea9b3e9dc985a5703f2e837da9bc08c28e4f633984 + + // The principal of this validator (32 ETH) is now stored in the eigenPod of the StakingNode with `nodeId`. + // This means that the effectiveBalanceGwei in the proof submitted to eigenlayer of this validator is 0. + + // Verifies the withdrawal credentials for the validator. + // This proves the validator's withdrawal address is set to be the eigenPod of the StakingNode with `nodeId`. + + { + // verifyWithdrawalCredentials + // setupForVerifyWithdrawalCredentials(state.nodeId, "test/data/holesky_wc_proof_1916455.json"); + ValidatorProofs memory validatorProofs = getWithdrawalCredentialParams(); + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + state.stakingNodeInstance.verifyWithdrawalCredentials( + uint64(block.timestamp), + validatorProofs.stateRootProof, + validatorProofs.validatorIndices, + validatorProofs.withdrawalCredentialProofs, + validatorProofs.validatorFields + ); + } + + // After proving the validator, Eigenlayer acnkowledges its existence and credits shares to the Owner + // of the eigenPod (which is the StakingNode) based the effectiveBalanceGwei of the validator. + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + + // To kickstart the withdrawal of the principal of the validator which now resides in the eigenPod, + // queueWithdrawals is executed for a desired withdrawalAmount. + state.stakingNodeInstance.queueWithdrawals(state.withdrawalAmount); + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + // After delegationManager.minWithdrawalDelayBlocks(), withdrawals can now be completed. + completeQueuedWithdrawals(state.stakingNodeInstance, state.withdrawalAmount); + + // After Withdrawals are completed, the ETH principal now resides in the StakingNode contract + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 ethEquivalent = yneth.previewRedeem(userRequestedAmountYnETH); + + // This next step involves withdrawing the prinicipal from the StakingNode instance and distributing that + // for reinvesmtent or to the withdrawal assets vault to fulfil the users' withdrawal requests. + + uint256 tokenId; + { + uint256 ynETHBalanceBefore = yneth.balanceOf(userAddress); + vm.prank(userAddress); + yneth.approve(address(ynETHWithdrawalQueueManager), userRequestedAmountYnETH); + vm.prank(userAddress); + tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(userRequestedAmountYnETH); + uint256 ynETHBalanceAfter = yneth.balanceOf(userAddress); + assertEq(ynETHBalanceBefore - ynETHBalanceAfter, userRequestedAmountYnETH, "ynETH balance after withdrawal request does not match expected amount"); + } + + runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore); + + uint256 systemAmountToWithdraw = ethEquivalent * 4; + uint256 amountToReinvest = ethEquivalent; + uint256 amountToQueue = systemAmountToWithdraw - amountToReinvest; + + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + IStakingNodesManager.WithdrawalAction[] memory actions = new IStakingNodesManager.WithdrawalAction[](1); + actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: state.nodeId, + amountToReinvest: amountToReinvest, + amountToQueue: amountToQueue, + rewardsAmount: 0 + }); + stakingNodesManager.processPrincipalWithdrawals(actions); + + runSystemStateInvariants( + state.totalAssetsBefore, + state.totalSupplyBefore, + state.stakingNodeBalancesBefore, + actions, + state.previousYnETHRedemptionAssetsVaultBalance, + state.previousYnETHBalance + ); + + // Mark witdrawals as finalized up to tokenId index + uint256 finalizationId = finalizeRequest(tokenId); + + uint256 userEthBalanceBefore = receivalAddress.balance; + uint256 ynETHRedemptionAssetsVaultBalanceBefore = ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(); + + vm.prank(userAddress); + ynETHWithdrawalQueueManager.claimWithdrawal(IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + receiver: receivalAddress, + finalizationId: finalizationId + })); + + uint256 userEthBalanceAfter = receivalAddress.balance; + + uint256 feePercentage = ynETHWithdrawalQueueManager.withdrawalFee(); + uint256 feeAmount = (ethEquivalent * feePercentage) / ynETHWithdrawalQueueManager.FEE_PRECISION(); + uint256 expectedReceivedAmount = ethEquivalent - feeAmount; + assertEq(userEthBalanceAfter - userEthBalanceBefore, expectedReceivedAmount, "ETH balance change does not match the expected ETH equivalent"); + + uint256 ynETHRedemptionAssetsVaultBalanceAfter = ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(); + assertEq( + ynETHRedemptionAssetsVaultBalanceBefore - ynETHRedemptionAssetsVaultBalanceAfter, + ethEquivalent, + "Difference in ynETH Redemption assets vault available assets does not match the expected ETH equivalent" + ); + } +} \ No newline at end of file diff --git a/test/scenarios/fork/ynETHWithdrawals.t.sol b/test/scenarios/fork/ynETHWithdrawals.t.sol new file mode 100644 index 000000000..fd1149426 --- /dev/null +++ b/test/scenarios/fork/ynETHWithdrawals.t.sol @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; + +import {IERC721} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {IWithdrawalQueueManager} from "../../../src/interfaces/IWithdrawalQueueManager.sol"; +import {IStakingNodesManager} from "../../../src/interfaces/IStakingNodesManager.sol"; +import {IRedeemableAsset} from "../../../src/interfaces/IRedeemableAsset.sol"; +import {IRedemptionAssetsVault} from "../../../src/interfaces/IRedemptionAssetsVault.sol"; +import {IynETH} from "../../../src/interfaces/IynETH.sol"; + +import {StakingNode} from "../../../src/StakingNode.sol"; +import {WithdrawalQueueManager} from "../../../src/WithdrawalQueueManager.sol"; +import {ynETHRedemptionAssetsVault, ETH_ASSET} from "../../../src/ynETHRedemptionAssetsVault.sol"; + +import "../../utils/StakingNodeTestBase.sol"; + +contract ynETHWithdrawalsOnHolesky is StakingNodeTestBase { + + error CallerNotOwnerNorApproved(uint256 tokenId, address caller); + error NotFinalized(uint256 currentTimestamp, uint256 requestTimestamp, uint256 queueDuration); + error ERC721NonexistentToken(uint256 tokenId); + error InsufficientBalance(uint256 currentBalance, uint256 requestedBalance); + error InsufficientAssetBalance(address asset, uint256 requestedAmount, uint256 balance); + error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); + error AmountExceedsSurplus(uint256 requestedAmount, uint256 availableSurplus); + error SecondsToFinalizationExceedsLimit(uint256 value); + + bool public isHolesky; + + address public constant user = address(0x12345678); + address public constant receiver = address(0x987654321); + + uint256 public nodeId = 2; + uint256 public withdrawalAmount = 32 ether; + + uint256 tokenId; + uint256 secondsToFinalization; + uint256 amountOut; + + StakingNode public stakingNode; + + // ------------------------------------------ + // Setup + // ------------------------------------------ + + function setUp() public override { + super.setUp(); + + isHolesky = block.chainid == 17000; + + if(isHolesky) { + stakingNode = StakingNode(payable(address(stakingNodesManager.nodes(nodeId)))); + + vm.deal(user, 10_000 ether); + + // setupForVerifyWithdrawalCredentials(nodeId, "test/data/holesky_wc_proof_1916455.json"); + } + } + + // ------------------------------------------ + // Withdrawal flow tests + // ------------------------------------------ + + function testVerifyWithdrawalCredentials() public { + if (!isHolesky) return; + + ValidatorProofs memory _validatorProofs = getWithdrawalCredentialParams(); + + uint256 _unverifiedStakedETH = stakingNode.unverifiedStakedETH(); + + vm.prank(actors.ops.STAKING_NODES_OPERATOR); + stakingNode.verifyWithdrawalCredentials( + uint64(block.timestamp), + _validatorProofs.stateRootProof, + _validatorProofs.validatorIndices, + _validatorProofs.withdrawalCredentialProofs, + _validatorProofs.validatorFields + ); + + assertEq(stakingNode.unverifiedStakedETH(), _unverifiedStakedETH, "testVerifyWithdrawalCredentials: E0"); // validator already verified withdrawal credentials + } + + function testVerifyWithdrawalCredentialsWrongCaller() public { + if (!isHolesky) return; + + ValidatorProofs memory _validatorProofs = getWithdrawalCredentialParams(); + + vm.expectRevert(bytes4(keccak256("NotStakingNodesOperator()"))); + stakingNode.verifyWithdrawalCredentials( + uint64(block.timestamp), + _validatorProofs.stateRootProof, + _validatorProofs.validatorIndices, + _validatorProofs.withdrawalCredentialProofs, + _validatorProofs.validatorFields + ); + } + + function testQueueWithdrawal() public { + if (!isHolesky) return; + + testVerifyWithdrawalCredentials(); + + uint256 _queuedSharesAmountBefore = stakingNode.queuedSharesAmount(); + + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNode.queueWithdrawals(withdrawalAmount); + + assertEq(stakingNode.queuedSharesAmount(), _queuedSharesAmountBefore + withdrawalAmount, "testQueueWithdrawal: E0"); + + _queuedSharesAmountBefore = stakingNode.queuedSharesAmount(); + uint256 _balanceBefore = address(stakingNode).balance; + uint256 _withdrawnETHBefore = stakingNode.withdrawnETH(); + completeQueuedWithdrawals(stakingNode, withdrawalAmount); + assertEq(address(stakingNode).balance - _balanceBefore, withdrawalAmount, "testQueueWithdrawal: E1"); + assertEq(stakingNode.queuedSharesAmount(), _queuedSharesAmountBefore - withdrawalAmount, "testQueueWithdrawal: E2"); + assertEq(stakingNode.withdrawnETH(), _withdrawnETHBefore + withdrawalAmount, "testQueueWithdrawal: E3"); + } + + function testQueueWithdrawalWrongCaller() public { + if (!isHolesky) return; + + vm.expectRevert(bytes4(keccak256("NotStakingNodesWithdrawer()"))); + stakingNode.queueWithdrawals(withdrawalAmount); + } + + function testRequestWithdrawal(uint256 _amount) public { + if (!isHolesky) return; + + vm.assume(_amount > 0 && _amount < 10_000 ether); + + testQueueWithdrawal(); + + uint256 _expectedTokenAmount = yneth.previewDeposit(_amount); + if (_expectedTokenAmount == 0) { + // PASS, there's nothing to evaluate for this deposit. + return; + } + + { + vm.prank(user); + uint256 _actualAmount = yneth.depositETH{value: _amount}(user); + assertEq(_actualAmount, _expectedTokenAmount, "testRequestWithdrawal: E0"); + } + + uint256 _ynETHBalanceBefore = yneth.balanceOf(user); + // 1 ynETH is at least 1 ETH in value + assertLe(_ynETHBalanceBefore, _amount, "testRequestWithdrawal: E1"); + + uint256 _expectedAmountOut = yneth.previewRedeem(_expectedTokenAmount); + uint256 _queueManagerBalanceBefore = yneth.balanceOf(address(ynETHWithdrawalQueueManager)); + uint256 _pendingRequestedRedemptionAmountBefore = ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(); + + vm.startPrank(user); + yneth.approve(address(ynETHWithdrawalQueueManager), _expectedTokenAmount); + tokenId = ynETHWithdrawalQueueManager.requestWithdrawal(_expectedTokenAmount); + vm.stopPrank(); + + amountOut = ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount() - _pendingRequestedRedemptionAmountBefore; + + assertEq(yneth.balanceOf(user), 0, "testRequestWithdrawal: E2"); + assertGt(ynETHWithdrawalQueueManager.balanceOf(user), 0, "testRequestWithdrawal: E3"); + assertEq(ynETHWithdrawalQueueManager.ownerOf(tokenId), user, "testRequestWithdrawal: E4"); + assertEq(yneth.balanceOf(address(ynETHWithdrawalQueueManager)) - _queueManagerBalanceBefore, _expectedTokenAmount, "testRequestWithdrawal: E5"); + assertApproxEqAbs(amountOut, _expectedAmountOut, 100_000, "testRequestWithdrawal: E6"); + assertGe(_expectedAmountOut, amountOut, "testRequestWithdrawal: E7"); + + { + IWithdrawalQueueManager.WithdrawalRequest memory _request = ynETHWithdrawalQueueManager.withdrawalRequest(tokenId); + assertEq(_request.amount, _expectedTokenAmount, "testRequestWithdrawal: E8"); + assertEq(ynETHWithdrawalQueueManager.withdrawalFee(), _request.feeAtRequestTime, "testRequestWithdrawal: E9"); + assertEq(yneth.previewRedeem(1 ether), _request.redemptionRateAtRequestTime, "testRequestWithdrawal: E10"); + assertEq(_request.creationTimestamp, block.timestamp, "testRequestWithdrawal: E11"); + assertTrue(!_request.processed, "testRequestWithdrawal: E12"); + } + } + + function testProcessPrincipalWithdrawalsForNode() public { + if (!isHolesky) return; + + testRequestWithdrawal(withdrawalAmount); + uint256 _withdrawnETHBefore = stakingNode.withdrawnETH(); + uint256 _stakingNodesManagerBalanceBefore = address(stakingNodesManager).balance; + uint256 _ynETHBalanceBefore = address(yneth).balance; + uint256 _withdrawalAssetsVaultBalanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + uint256 _availableRedemptionAssetsBefore = ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(); + + IStakingNodesManager.WithdrawalAction[] memory _actions = new IStakingNodesManager.WithdrawalAction[](1); + _actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: 0, + amountToQueue: withdrawalAmount, + rewardsAmount: 0 + }); + + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + stakingNodesManager.processPrincipalWithdrawals(_actions); + + assertEq(stakingNode.withdrawnETH() + withdrawalAmount, _withdrawnETHBefore, "testProcessPrincipalWithdrawalsForNode: E0"); + assertEq(address(stakingNodesManager).balance, _stakingNodesManagerBalanceBefore, "testProcessPrincipalWithdrawalsForNode: E1"); + assertEq(address(yneth).balance, _ynETHBalanceBefore, "testProcessPrincipalWithdrawalsForNode: E2"); + assertEq(address(ynETHRedemptionAssetsVaultInstance).balance, _withdrawalAssetsVaultBalanceBefore + withdrawalAmount, "testProcessPrincipalWithdrawalsForNode: E3"); + assertEq(ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(), _availableRedemptionAssetsBefore + withdrawalAmount, "testProcessPrincipalWithdrawalsForNode: E4"); + } + + function testProcessPrincipalWithdrawalsForNodeWrongCaller() public { + if (!isHolesky) return; + + IStakingNodesManager.WithdrawalAction[] memory _actions = new IStakingNodesManager.WithdrawalAction[](1); + _actions[0] = IStakingNodesManager.WithdrawalAction({ + nodeId: nodeId, + amountToReinvest: 0, + amountToQueue: withdrawalAmount, + rewardsAmount: 0 + }); + + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), stakingNodesManager.WITHDRAWAL_MANAGER_ROLE())); + stakingNodesManager.processPrincipalWithdrawals(_actions); + } + + function testClaimWithdrawalInsufficientBalance() public { + if (!isHolesky) return; + + testRequestWithdrawal(withdrawalAmount); + + finalizeRequest(tokenId); + + // vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector, address(ynETHWithdrawalQueueManager).balance, withdrawalAmount - 40)); + // fails with above error, but `_amount` varies because of little precision loss + // vm.expectRevert(); + // vm.prank(user); + // ynETHWithdrawalQueueManager.claimWithdrawal(tokenId, receiver); + } + + function testClaimWithdrawalOnHolesky() public { + if (!isHolesky) return; + + testProcessPrincipalWithdrawalsForNode(); + + uint256 finalizationIndex = finalizeRequest(tokenId); + + uint256 _pendingRequestedRedemptionAmountBefore = ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(); + uint256 _totalSupplyBefore = yneth.totalSupply(); + uint256 _receiverBalanceBefore = address(receiver).balance; + uint256 _feeReceiverBalanceBefore = address(ynETHWithdrawalQueueManager.feeReceiver()).balance; + + vm.prank(user); + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + finalizationId: finalizationIndex, + receiver: receiver + }); + ynETHWithdrawalQueueManager.claimWithdrawals(claims); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + ynETHWithdrawalQueueManager.ownerOf(tokenId); + + (uint256 _reqAmount, uint256 _feeAtRequestTime,,, bool _processed,) = ynETHWithdrawalQueueManager.withdrawalRequests(tokenId); + assertTrue(_processed, "testClaimWithdrawal: E0"); + + uint256 _feeAmount = ynETHWithdrawalQueueManager.calculateFee(amountOut, _feeAtRequestTime); + uint256 _receiverAmount = amountOut - _feeAmount; + + assertEq(ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(), _pendingRequestedRedemptionAmountBefore - amountOut, "testClaimwithdrawal: E1"); + assertEq(yneth.totalSupply(), _totalSupplyBefore - _reqAmount, "testClaimwithdrawal: E2"); + assertEq(address(receiver).balance - _receiverBalanceBefore, _receiverAmount, "testClaimwithdrawal: E3"); + assertEq(address(ynETHWithdrawalQueueManager.feeReceiver()).balance - _feeReceiverBalanceBefore, _feeAmount, "testClaimwithdrawal: E4"); + } + + function testClaimWithdrawalWrongCaller() public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(CallerNotOwnerNorApproved.selector, tokenId, address(this))); + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + finalizationId: 0, // Assuming 0 as we don't have a specific finalizationId in this context + receiver: receiver + }); + ynETHWithdrawalQueueManager.claimWithdrawals(claims); + } + + function testClaimWithdrawalNotFinalized() public { + if (!isHolesky) return; + + testProcessPrincipalWithdrawalsForNode(); + + vm.expectRevert(abi.encodeWithSelector(NotFinalized.selector, tokenId, block.timestamp, block.timestamp)); + vm.prank(user); + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + finalizationId: 0, // Assuming 0 as we don't have a specific finalizationId in this context + receiver: receiver + }); + ynETHWithdrawalQueueManager.claimWithdrawals(claims); + } + + // ------------------------------------------ + // Unit tests - WithdrawalQueueManager + // ------------------------------------------ + + function testInitializationManager() public { + if (!isHolesky) return; + + TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( + address(new WithdrawalQueueManager()), + actors.admin.PROXY_ADMIN_OWNER, + "" + ); + WithdrawalQueueManager _manager = WithdrawalQueueManager(address(_proxy)); + + string memory _name = "ynETH Withdrawal Manager"; + string memory _symbol = "ynETHWM"; + address _redeemableAsset = address(yneth); + address _redemptionAssetsVault = address(ynETHRedemptionAssetsVaultInstance); + address _admin = actors.admin.PROXY_ADMIN_OWNER; + address _withdrawalQueueAdmin = actors.ops.WITHDRAWAL_MANAGER; + address _requestFinalizer = actors.ops.REQUEST_FINALIZER; + uint256 _withdrawalFee = 500; + address _feeReceiver = actors.admin.FEE_RECEIVER; + WithdrawalQueueManager.Init memory _init = WithdrawalQueueManager.Init({ + name: _name, + symbol: _symbol, + redeemableAsset: IRedeemableAsset(_redeemableAsset), + redemptionAssetsVault: IRedemptionAssetsVault(_redemptionAssetsVault), + admin: _admin, + withdrawalQueueAdmin: _withdrawalQueueAdmin, + redemptionAssetWithdrawer: _withdrawalQueueAdmin, + requestFinalizer: _requestFinalizer, + withdrawalFee: _withdrawalFee, + feeReceiver: _feeReceiver + }); + _manager.initialize(_init); + + assertEq(_manager.name(), _name, "testInitialization: E0"); + assertEq(_manager.symbol(), _symbol, "testInitialization: E1"); + assertEq(address(_manager.redeemableAsset()), _redeemableAsset, "testInitialization: E2"); + assertEq(address(_manager.redemptionAssetsVault()), _redemptionAssetsVault, "testInitialization: E3"); + assertEq(_manager.withdrawalFee(), _withdrawalFee, "testInitialization: E4"); + assertEq(address(_manager.feeReceiver()), _feeReceiver, "testInitialization: E5"); + assertEq(_manager.hasRole(_manager.DEFAULT_ADMIN_ROLE(), _admin), true, "testInitialization: E6"); + assertEq(_manager.hasRole(_manager.WITHDRAWAL_QUEUE_ADMIN_ROLE(), _withdrawalQueueAdmin), true, "testInitialization: E7"); + } + + function testInitializationManagerInvalid() public { + if (!isHolesky) return; + + string memory _name = "ynETH Withdrawal Manager"; + string memory _symbol = "ynETHWM"; + address _redeemableAsset = address(yneth); + address _redemptionAssetsVault = address(ynETHRedemptionAssetsVaultInstance); + address _admin = actors.admin.PROXY_ADMIN_OWNER; + address _withdrawalQueueAdmin = actors.ops.WITHDRAWAL_MANAGER; + address _requestFinalizer = actors.ops.REQUEST_FINALIZER; + uint256 _withdrawalFee = 500; + address _feeReceiver = actors.admin.FEE_RECEIVER; + WithdrawalQueueManager.Init memory _init = WithdrawalQueueManager.Init({ + name: _name, + symbol: _symbol, + redeemableAsset: IRedeemableAsset(_redeemableAsset), + redemptionAssetsVault: IRedemptionAssetsVault(_redemptionAssetsVault), + admin: _admin, + withdrawalQueueAdmin: _withdrawalQueueAdmin, + redemptionAssetWithdrawer: _withdrawalQueueAdmin, + requestFinalizer: _requestFinalizer, + withdrawalFee: _withdrawalFee, + feeReceiver: _feeReceiver + }); + + vm.expectRevert(bytes4(keccak256("InvalidInitialization()"))); + ynETHWithdrawalQueueManager.initialize(_init); + } + + function testSetWithdrawalFee(uint256 _feePercentage) public { + if (!isHolesky) return; + + if (_feePercentage <= ynETHWithdrawalQueueManager.FEE_PRECISION()) { + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + ynETHWithdrawalQueueManager.setWithdrawalFee(_feePercentage); + assertEq(ynETHWithdrawalQueueManager.withdrawalFee(), _feePercentage, "testSetWithdrawalFee: E0"); + } + } + + function testSetWithdrawalFeeWrongCaller(uint256 _feePercentage) public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), ynETHWithdrawalQueueManager.WITHDRAWAL_QUEUE_ADMIN_ROLE())); + ynETHWithdrawalQueueManager.setWithdrawalFee(_feePercentage); + } + + function testSetWithdrawalFeeWrongPercentage(uint256 _feePercentage) public { + if (!isHolesky) return; + + if (_feePercentage > ynETHWithdrawalQueueManager.FEE_PRECISION()) { + vm.expectRevert(bytes4(keccak256("FeePercentageExceedsLimit()"))); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + ynETHWithdrawalQueueManager.setWithdrawalFee(_feePercentage); + } + } + + function testFeeReceiver(address _feeReceiver) public { + if (!isHolesky) return; + + if (_feeReceiver != address(0)) { + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + ynETHWithdrawalQueueManager.setFeeReceiver(_feeReceiver); + assertEq(ynETHWithdrawalQueueManager.feeReceiver(), _feeReceiver, "testFeeReceiver: E0"); + } + } + + function testFeeReceiverWrongCaller(address _feeReceiver) public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), ynETHWithdrawalQueueManager.WITHDRAWAL_QUEUE_ADMIN_ROLE())); + ynETHWithdrawalQueueManager.setFeeReceiver(_feeReceiver); + } + + function testFeeReceiverZeroAddress() public { + if (!isHolesky) return; + + address _feeReceiver = address(0); + vm.expectRevert(bytes4(keccak256("ZeroAddress()"))); + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + ynETHWithdrawalQueueManager.setFeeReceiver(_feeReceiver); + } + + function testSurplusRedemptionAssets() public { + if (!isHolesky) return; + + uint256 _balanceOfVaultBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + assertEq(ynETHWithdrawalQueueManager.surplusRedemptionAssets(), _balanceOfVaultBefore - ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(), "testSurplusRedemptionAssets: E0"); + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), 1 ether); + assertEq(ynETHWithdrawalQueueManager.surplusRedemptionAssets(), _balanceOfVaultBefore - ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount() + 1 ether, "testSurplusRedemptionAssets: E1"); + } + + function testDeficitRedemptionAssets() public { + if (!isHolesky) return; + + uint256 _deficitBefore = ynETHWithdrawalQueueManager.pendingRequestedRedemptionAmount(); + uint256 _vaultBalanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + if (_deficitBefore > _vaultBalanceBefore) { + assertEq(ynETHWithdrawalQueueManager.deficitRedemptionAssets(), _deficitBefore - _vaultBalanceBefore, "testDeficitRedemptionAssets: E0"); + } else { + assertEq(ynETHWithdrawalQueueManager.deficitRedemptionAssets(), 0, "testDeficitRedemptionAssets: E1"); + } + + testRequestWithdrawal(_vaultBalanceBefore + 1 ether); + assertGt(ynETHWithdrawalQueueManager.deficitRedemptionAssets(), _vaultBalanceBefore, "testDeficitRedemptionAssets: E2"); + assertApproxEqAbs(ynETHWithdrawalQueueManager.deficitRedemptionAssets(), _deficitBefore + 1 ether - _vaultBalanceBefore, 1e5, "testDeficitRedemptionAssets: E3"); + } + + function testWithdrawSurplusRedemptionAssets(uint256 _amount) public { + if (!isHolesky) return; + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), 10 ether); + uint256 _surplusBefore = ynETHWithdrawalQueueManager.surplusRedemptionAssets(); + uint256 _vaultBalanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + uint256 _ynETHBalanceBefore = address(yneth).balance; + + if (_amount <= _surplusBefore) { + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + ynETHWithdrawalQueueManager.withdrawSurplusRedemptionAssets(_amount); + assertEq(ynETHWithdrawalQueueManager.surplusRedemptionAssets(), _surplusBefore - _amount, "testWithdrawSurplusRedemptionAssets: E0"); + assertEq(address(ynETHRedemptionAssetsVaultInstance).balance, _vaultBalanceBefore - _amount, "testWithdrawSurplusRedemptionAssets: E1"); + assertEq(address(yneth).balance - _ynETHBalanceBefore, _amount, "testWithdrawSurplusRedemptionAssets: E2"); + } + } + + function testWithdrawSurplusRedemptionAssetsAmountExceedsSurplus(uint256 _amount) public { + if (!isHolesky) return; + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), 10 ether); + uint256 _surplusBefore = ynETHWithdrawalQueueManager.surplusRedemptionAssets(); + if (_amount > _surplusBefore) { + vm.prank(actors.ops.WITHDRAWAL_MANAGER); + vm.expectRevert(abi.encodeWithSelector(AmountExceedsSurplus.selector, _amount, _surplusBefore)); + ynETHWithdrawalQueueManager.withdrawSurplusRedemptionAssets(_amount); + } + } + + function testWithdrawSurplusRedemptionAssetsWrongCaller(uint256 _amount) public { + if (!isHolesky) return; + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), 10 ether); + uint256 _surplusBefore = ynETHWithdrawalQueueManager.surplusRedemptionAssets(); + if (_amount <= _surplusBefore) { + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), ynETHWithdrawalQueueManager.REDEMPTION_ASSET_WITHDRAWER_ROLE())); + ynETHWithdrawalQueueManager.withdrawSurplusRedemptionAssets(_amount); + } + } + + function testSupportsInterface(bytes4 _interfaceId) public { + if (!isHolesky) return; + + bool _expected = _interfaceId == type(IERC721).interfaceId; + assertEq(ynETHWithdrawalQueueManager.supportsInterface(_interfaceId), _expected, "testSupportsInterface: E0"); + } + + // ------------------------------------------ + // Unit tests - ynETHRedemptionAssetsVault + // ------------------------------------------ + + function testInitializationVault() public { + if (!isHolesky) return; + + TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( + address(new ynETHRedemptionAssetsVault()), + actors.admin.PROXY_ADMIN_OWNER, + "" + ); + ynETHRedemptionAssetsVault _ynETHRedemptionAssetsVault = ynETHRedemptionAssetsVault(payable(address(_proxy))); + + address _admin = actors.admin.PROXY_ADMIN_OWNER; + address _redeemer = address(ynETHWithdrawalQueueManager); + address _ynETH = address(yneth); + ynETHRedemptionAssetsVault.Init memory _init = ynETHRedemptionAssetsVault.Init({ + admin: _admin, + redeemer: _redeemer, + ynETH: IynETH(address(_ynETH)) + }); + _ynETHRedemptionAssetsVault.initialize(_init); + + assertEq(address(ynETHRedemptionAssetsVaultInstance.ynETH()), _ynETH, "testInitializationVault: E0"); + assertEq(ynETHRedemptionAssetsVaultInstance.hasRole(ynETHRedemptionAssetsVaultInstance.DEFAULT_ADMIN_ROLE(), _admin), true, "testInitializationVault: E1"); + assertEq(ynETHRedemptionAssetsVaultInstance.redeemer(), _redeemer, "testInitializationVault: E2"); + assertEq(ynETHRedemptionAssetsVaultInstance.hasRole(ynETHRedemptionAssetsVaultInstance.PAUSER_ROLE(), _admin), true, "testInitializationVault: E3"); + assertEq(ynETHRedemptionAssetsVaultInstance.hasRole(ynETHRedemptionAssetsVaultInstance.UNPAUSER_ROLE(), _admin), true, "testInitializationVault: E4"); + assertEq(ynETHRedemptionAssetsVaultInstance.paused(), false, "testInitializationVault: E5"); + } + + function testInitializationVaultInvalid() public { + if (!isHolesky) return; + + address _admin = actors.admin.PROXY_ADMIN_OWNER; + address _redeemer = address(ynETHWithdrawalQueueManager); + address _ynETH = address(yneth); + ynETHRedemptionAssetsVault.Init memory _init = ynETHRedemptionAssetsVault.Init({ + admin: _admin, + redeemer: _redeemer, + ynETH: IynETH(address(_ynETH)) + }); + + vm.expectRevert(bytes4(keccak256("InvalidInitialization()"))); + ynETHRedemptionAssetsVaultInstance.initialize(_init); + } + + function testRedemptionRate() public { + if (!isHolesky) return; + + uint256 _expected = yneth.previewRedeem(1 ether); + assertEq(ynETHRedemptionAssetsVaultInstance.redemptionRate(), _expected, "testRedemptionRate: E0"); + } + + function testAvailableRedemptionAssets(uint256 _dealAmount) public { + if (!isHolesky) return; + + uint256 _expected = address(ynETHRedemptionAssetsVaultInstance).balance; + assertEq(ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(), _expected, "testAvailableRedemptionAssets: E0"); + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), _dealAmount); + assertEq(ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(), _expected + _dealAmount, "testAvailableRedemptionAssets: E1"); + } + + function testTransferRedemptionAssets(address _to, uint256 _amount, uint256 _dealAmount) public { + if (!isHolesky) return; + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), _dealAmount); + uint256 _balanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + uint256 _toBalanceBefore = address(_to).balance; + + if (_balanceBefore >= _amount) { + vm.prank(address(ynETHWithdrawalQueueManager)); + ynETHRedemptionAssetsVaultInstance.transferRedemptionAssets(_to, _amount, ""); + assertEq(address(ynETHRedemptionAssetsVaultInstance).balance, _balanceBefore - _amount, "testTransferRedemptionAssets: E0"); + assertEq(address(_to).balance - _toBalanceBefore, _amount, "testTransferRedemptionAssets: E1"); + } + } + + function testTransferRedemptionAssetsInsufficientAssetBalance(address _to, uint256 _amount, uint256 _dealAmount) public { + if (!isHolesky) return; + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), _dealAmount); + uint256 _balanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + if (_balanceBefore < _amount) { + vm.expectRevert(abi.encodeWithSelector(InsufficientAssetBalance.selector, ETH_ASSET, _amount, _balanceBefore)); + vm.prank(address(ynETHWithdrawalQueueManager)); + ynETHRedemptionAssetsVaultInstance.transferRedemptionAssets(_to, _amount, ""); + } + } + + function testTransferRedemptionAssetsWrongCaller(address _to, uint256 _amount) public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(ynETHRedemptionAssetsVault.NotRedeemer.selector, address(this))); + ynETHRedemptionAssetsVaultInstance.transferRedemptionAssets(_to, _amount, ""); + } + + function testWithdrawRedemptionAssets(uint256 _amount) public { + if (!isHolesky) return; + + vm.assume(_amount < 100_000 ether); + + vm.deal(address(ynETHRedemptionAssetsVaultInstance), _amount); + uint256 _balanceBefore = address(ynETHRedemptionAssetsVaultInstance).balance; + uint256 _ynETHBalanceBefore = address(yneth).balance; + + vm.prank(address(ynETHWithdrawalQueueManager)); + ynETHRedemptionAssetsVaultInstance.withdrawRedemptionAssets(_amount); + + assertEq(address(ynETHRedemptionAssetsVaultInstance).balance, _balanceBefore - _amount, "testWithdrawRedemptionAssets: E0"); + assertEq(address(yneth).balance - _ynETHBalanceBefore, _amount, "testWithdrawRedemptionAssets: E1"); + } + + function testWithdrawRedemptionAssetsWrongCaller() public { + if (!isHolesky) return; + + uint256 _amount = 1 ether; + vm.expectRevert(abi.encodeWithSelector(ynETHRedemptionAssetsVault.NotRedeemer.selector, address(this))); + ynETHRedemptionAssetsVaultInstance.withdrawRedemptionAssets(_amount); + } + + function testPause() public { + if (!isHolesky) return; + + vm.prank(actors.admin.PROXY_ADMIN_OWNER); + ynETHRedemptionAssetsVaultInstance.pause(); + assertEq(ynETHRedemptionAssetsVaultInstance.paused(), true, "testPause: E0"); + + vm.expectRevert(bytes4(keccak256("ContractPaused()"))); + vm.prank(address(ynETHWithdrawalQueueManager)); + ynETHRedemptionAssetsVaultInstance.transferRedemptionAssets(address(this), 1 ether, ""); + + vm.expectRevert(bytes4(keccak256("ContractPaused()"))); + vm.prank(address(ynETHWithdrawalQueueManager)); + ynETHRedemptionAssetsVaultInstance.withdrawRedemptionAssets(1 ether); + } + + function testPauseWrongCaller() public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), ynETHRedemptionAssetsVaultInstance.PAUSER_ROLE())); + ynETHRedemptionAssetsVaultInstance.pause(); + } + + function testUnpause() public { + if (!isHolesky) return; + + vm.prank(actors.admin.PROXY_ADMIN_OWNER); + ynETHRedemptionAssetsVaultInstance.unpause(); + assertEq(ynETHRedemptionAssetsVaultInstance.paused(), false, "testUnpause: E0"); + } + + function testUnpauseWrongCaller() public { + if (!isHolesky) return; + + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), ynETHRedemptionAssetsVaultInstance.UNPAUSER_ROLE())); + ynETHRedemptionAssetsVaultInstance.unpause(); + } +} \ No newline at end of file diff --git a/test/scenarios/ynETH.spec.sol b/test/scenarios/ynETH.spec.sol index 162db6bff..635883b9a 100644 --- a/test/scenarios/ynETH.spec.sol +++ b/test/scenarios/ynETH.spec.sol @@ -9,9 +9,8 @@ import { IYnETHEvents } from "src/ynETH.sol"; import { BeaconChainProofs } from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; import { IEigenPod } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol"; import { IEigenPodManager } from "lib/eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; -import { IDelayedWithdrawalRouter } from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; +// import { IDelayedWithdrawalRouter } from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; import { IRewardsDistributor } from "src/interfaces/IRewardsDistributor.sol"; -import { ProofUtils } from "test/utils/ProofUtils.sol"; import "forge-std/Vm.sol"; contract YnETHScenarioTest1 is IntegrationBaseTest { @@ -216,106 +215,10 @@ contract YnETHScenarioTest3 is IntegrationBaseTest { return (stakingNode, validatorData); } - - function verifyEigenWithdrawCredentials(IStakingNode stakingNode) public { - - ProofUtils proofUtils = new ProofUtils("lib/eigenlayer-contracts/src/test/test-data/fullWithdrawalProof_Latest.json"); - - uint64 oracleTimestamp = uint64(block.timestamp); - - BeaconChainProofs.StateRootProof memory stateRootProof = proofUtils._getStateRootProof(); - - uint40[] memory validatorIndexes = new uint40[](1); - - validatorIndexes[0] = uint40(proofUtils.getValidatorIndex()); - - bytes[] memory validatorFieldsProofs = proofUtils._getValidatorFieldsProof(); - - bytes32[][] memory validatorFields = new bytes32[][](1); - validatorFields[0] = proofUtils.getValidatorFields(); - - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNode.verifyWithdrawalCredentials( - oracleTimestamp, - stateRootProof, - validatorIndexes, - validatorFieldsProofs, - validatorFields - ); - - IEigenPod eigenPod = IEigenPod(stakingNode.eigenPod()); - eigenPod.validatorStatus(0); - } } event LogUint(string message, uint256 value); -contract YnETHScenarioTest8 is IntegrationBaseTest, YnETHScenarioTest3 { - - /** - Scenario 8: Staking Rewards Distribution - Objective: Test the distribution of staking rewards to a multisig. - */ - - event Log(string message, uint256 value); - event LogAddress(string message, address value); - - function test_ynETH_Scenario_8_NonBeaconChainETH_Rewards_Distribution(uint256 randomAmount) public { - vm.assume(randomAmount > 32 ether + 2 wei && randomAmount < 100_000_000 ether); - - // Deposit 32 ETH to ynETH and create a Staking Node with a Validator - (IStakingNode stakingNode,) = depositEth_and_createValidator(); - - // send concensus rewards to eigen pod - uint256 amount = randomAmount; - deal(address(this), randomAmount); - - // transfer eth to the eigen pod - IEigenPod eigenPod = IEigenPod(stakingNode.eigenPod()); - (bool success,) = address(eigenPod).call{value: amount}(bytes("")); - require(success, "transfer to eigen pod failed"); - - // trigger withdraw before restaking succesfully - vm.prank(actors.ops.STAKING_NODES_OPERATOR); - stakingNode.withdrawNonBeaconChainETHBalanceWei(); - - // // There should be a delayedWithdraw on the DelayedWithdrawalRouter - IDelayedWithdrawalRouter withdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory delayedWithdrawals = withdrawalRouter.getUserDelayedWithdrawals(address(stakingNode)); - assertEq(delayedWithdrawals.length, 1); - assertEq(delayedWithdrawals[0].amount, amount); - - // Because of the delay, the delayedWithdrawal should not be claimable yet - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory claimableDelayedWithdrawals = withdrawalRouter.getClaimableUserDelayedWithdrawals(address(stakingNode)); - assertEq(claimableDelayedWithdrawals.length, 0); - - // Move ahead in time to make the delayedWithdrawal claimable - vm.roll(block.number + withdrawalRouter.withdrawalDelayBlocks() + 1); - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory claimableDelayedWithdrawalsWarp = withdrawalRouter.getClaimableUserDelayedWithdrawals(address(stakingNode)); - assertEq(claimableDelayedWithdrawalsWarp.length, 1); - assertEq(claimableDelayedWithdrawalsWarp[0].amount, amount, "claimableDelayedWithdrawalsWarp[0].amount != 3 ether"); - - withdrawalRouter.claimDelayedWithdrawals(address(stakingNode), type(uint256).max); - - // We can now claim the Rewards from delayedWithdrawal - vm.prank(address(actors.ops.STAKING_NODES_OPERATOR)); - stakingNode.processDelayedWithdrawals(); - assertEq(address(stakingNode).balance, 0); - - // Get the rewards receiver addresses from the rewards distributor - IRewardsDistributor rewardsDistributor = IRewardsDistributor(stakingNodesManager.rewardsDistributor()); - address consensusLayerReceiver = address(rewardsDistributor.consensusLayerReceiver()); - - uint256 concensusRewards = consensusLayerReceiver.balance; - uint256 ynethBalance = address(yneth).balance; - - uint256 concensusRewardsExpected = amount; - assertEq(concensusRewards, concensusRewardsExpected, "concensusRewards != concensusRewardsExpected"); - - // until processed rewards should be in the consensusLayerReceiver - assertEq(ynethBalance, 0, "yneth.balance != 0"); - } -} contract YnETHScenarioTest10 is IntegrationBaseTest, YnETHScenarioTest3 { @@ -330,104 +233,6 @@ contract YnETHScenarioTest10 is IntegrationBaseTest, YnETHScenarioTest3 { vm.recordLogs(); } - function test_ynETH_Scenario_9_Self_Destruct_Attack() public { - - uint256 previousTotalDeposited = yneth.totalDepositedInPool(); - uint256 previousTotalShares = yneth.totalSupply(); - - - // Deposit 32 ETH to ynETH and create a Staking Node with a Validator - (IStakingNode stakingNode,) = depositEth_and_createValidator(); - - // Amount of ether to send via self-destruct - uint256 amountToSendViaSelfDestruct = 1 ether; - - // Ensure the test contract has enough ether to send, user1 comes from Test3 - vm.deal(user1, amountToSendViaSelfDestruct); - - // Address to send ether to - for example, the stakingNode or another address - address payable target = payable(address(stakingNode)); // or any other target address - - // Create and send ether via self-destruct - // The SelfDestructSender contract is created with the amountToSend and immediately self-destructs, - // sending its balance to the target address. - address(new SelfDestructSender{value: amountToSendViaSelfDestruct}(target)); - - log_balances(stakingNode); - - assertEq(address(yneth).balance, 0, "yneth.balance != 0"); - assertEq(address(stakingNode).balance, 1 ether, "stakingNode.balance != 1 ether"); - assertEq(address(consensusLayerReceiver).balance, 0, "consensusLayerReceiver.balance != 0"); - assertEq(address(executionLayerReceiver).balance, 0, "executionLayerReceiver.balance != 0"); - - vm.startPrank(actors.ops.STAKING_NODES_OPERATOR); - uint256 rewardsSentToEigenPod = send_eth_rewards_to_eigenpod(stakingNode); - stakingNode.processDelayedWithdrawals(); - vm.stopPrank(); - - log_balances(stakingNode); - - // funds are deposited in the validators - assertEq(address(yneth).balance, 0, "yneth.balance != 0"); - - // funds have been collected from the StakingNode - assertEq(address(stakingNode).balance, 0, "stakingNode.balance != 0"); - assertEq( - address(consensusLayerReceiver).balance, - rewardsSentToEigenPod + amountToSendViaSelfDestruct, - "consensusLayerReceiver.balance != 1 ether + 1 wei" - ); - assertEq(address(executionLayerReceiver).balance, 0, "executionLayerReceiver.balance != 0"); - - uint256 userAmount = 32 ether; - uint256 userShares = yneth.balanceOf(user1); - - runInvariants( - user1, - previousTotalDeposited, - previousTotalShares, - userAmount, - userShares - ); - } - - function send_eth_rewards_to_eigenpod(IStakingNode stakingNode) public returns (uint256) { - - // send concensus rewards to eigen pod - uint256 amount = 32 ether + 1 wei; - IEigenPod eigenPod = IEigenPod(stakingNode.eigenPod()); - uint256 initialPodBalance = address(eigenPod).balance; - - vm.deal(actors.ops.STAKING_NODES_OPERATOR, 40 ether); - (bool success,) = payable(address(eigenPod)).call{value: amount}(""); - require(success, "Failed to send rewards to EigenPod"); - - assertEq(address(eigenPod).balance, initialPodBalance + amount, "eigenPod.balance != initialPodBalance + amount"); - - // trigger withdraw before restaking succesfully - stakingNode.withdrawNonBeaconChainETHBalanceWei(); - - // There should be a delayedWithdraw on the DelayedWithdrawalRouter - IDelayedWithdrawalRouter withdrawalRouter = IDelayedWithdrawalRouter(chainAddresses.eigenlayer.DELAYED_WITHDRAWAL_ROUTER_ADDRESS); - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory delayedWithdrawals = withdrawalRouter.getUserDelayedWithdrawals(address(stakingNode)); - assertEq(delayedWithdrawals.length, 1); - assertEq(delayedWithdrawals[0].amount, amount); - - // Because of the delay, the delayedWithdrawal should not be claimable yet - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory claimableDelayedWithdrawals = withdrawalRouter.getClaimableUserDelayedWithdrawals(address(stakingNode)); - assertEq(claimableDelayedWithdrawals.length, 0, "claimableDelayedWithdrawals.length != 0"); - - // Move ahead in time to make the delayedWithdrawal claimable - vm.roll(block.number + withdrawalRouter.withdrawalDelayBlocks() + 1); - IDelayedWithdrawalRouter.DelayedWithdrawal[] memory claimableDelayedWithdrawalsWarp = withdrawalRouter.getClaimableUserDelayedWithdrawals(address(stakingNode)); - assertEq(claimableDelayedWithdrawalsWarp.length, 1, "claimableDelayedWithdrawalsWarp.length != 1"); - assertEq(claimableDelayedWithdrawalsWarp[0].amount, amount, "claimableDelayedWithdrawalsWarp[0].amount != 3 ether"); - - withdrawalRouter.claimDelayedWithdrawals(address(stakingNode), type(uint256).max); - - return amount; - } - function log_balances (IStakingNode stakingNode) public { emit LogUint("yneth.balance", address(yneth).balance); emit LogUint("stakingNode.balance", address(stakingNode).balance); diff --git a/test/scenarios/ynLSD.spec.sol b/test/scenarios/ynLSD.spec.sol deleted file mode 100644 index a4550fe41..000000000 --- a/test/scenarios/ynLSD.spec.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.24; - -import { IntegrationBaseTest } from "test/integration/IntegrationBaseTest.sol"; -import { Invariants } from "test/scenarios/Invariants.sol"; -import { IERC20 } from "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol"; -import { TestAssetUtils } from "test/utils/TestAssetUtils.sol"; - -contract YnLSDScenarioTest1 is IntegrationBaseTest { - - // /** - // Scenario 1: Successful LSD Deposit and Share Minting - // Objective: Test that a user can deposit stETH and receive - // the correct amount of shares in return. - // */ - - // function test_ynLSD_Scenario_1_Fuzz(uint256 amount1, uint256 amount2, uint256 amount3) public { - - // /** - // Users deposit random amounts - // - Check the total assets of ynLSD - // - Check the share balance of each user - // - Check total supply of ynLSD - // */ - - // address asset = chainAddresses.lsd.STETH_ADDRESS; - - // User_stETH_deposit(asset, amount1, address(0x01)); - // User_stETH_deposit(asset, amount2, address(0x02)); - // User_stETH_deposit(asset, amount3, address(0x03)); - // } - - // function User_stETH_deposit(address asset, uint256 amount, address user) public { - - // vm.assume(amount > 1 && amount < 10_000 ether); - - // uint256 previousTotalShares = ynlsd.totalSupply(); - // uint256 previousTotalAssets = ynlsd.getTotalAssets()[0]; - - // vm.startPrank(user); - // // 1. Obtain stETH and Deposit assets to ynLSD by User - // TestAssetUtils testAssetUtils = new TestAssetUtils(); - // IERC20 stETH = IERC20(asset); - // uint256 userDeposit = testAssetUtils.get_stETH(address(this), amount); - - // stETH.approve(address(ynlsd), userDeposit); - // ynlsd.deposit(stETH, userDeposit, user); - - // uint256 userShares = ynlsd.balanceOf(user); - - // uint256 currentTotalAssets = ynlsd.getTotalAssets()[0]; - // uint256 currentTotalShares = ynlsd.totalSupply(); - - // runInvariants( - // user, - // previousTotalAssets, - // previousTotalShares, - // currentTotalAssets, - // currentTotalShares, - // userDeposit, - // userShares - // ); - // } - - // function runInvariants( - // address user, - // uint256 previousTotalAssets, - // uint256 previousTotalShares, - // uint256 currentTotalAssets, - // uint256 currentTotalShares, - // uint256 userDeposit, - // uint256 userShares - // ) public view{ - // Invariants.totalAssetsIntegrity(currentTotalAssets, previousTotalAssets, userDeposit); - // Invariants.shareMintIntegrity(currentTotalShares, previousTotalShares, userShares); - // Invariants.userSharesIntegrity(ynlsd.balanceOf(user), 0, userShares); - // } -} \ No newline at end of file diff --git a/test/unit/mocks/MockRedeemableYnETH.sol b/test/unit/mocks/MockRedeemableYnETH.sol new file mode 100644 index 000000000..d39a073b7 --- /dev/null +++ b/test/unit/mocks/MockRedeemableYnETH.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.24; + +import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import {ERC20Burnable} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import {IRedeemableAsset} from "src/interfaces/IRedeemableAsset.sol"; +import {IERC20Metadata} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "forge-std/console.sol"; + + +contract MockRedeemableYnETH is IRedeemableAsset, ERC20Burnable { + + uint256 public totalAssets; // ETH denominated + + constructor() ERC20("Mock Redeemable Asset", "MRA") { + } + + receive() external payable {} + + function processWithdrawnETH() external payable override { + return; + } + + function mint(address receiver, uint256 amount) external { + _mint(receiver, amount); + } + + function decimals() public view override(ERC20, IERC20Metadata) returns (uint8) { + return 18; + } + + function burn(uint256 amount) public override(IRedeemableAsset, ERC20Burnable) { + super.burn(amount); + } + + /** + * @notice Provides a preview of the amount of underlying asset that would be redeemed for a given amount of tokens. + * @param shares The amount of tokens to preview the redemption for. + * @return The amount of underlying asset that would be redeemed. + */ + function previewRedeem(uint256 shares) public view returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view returns (uint256) { + + uint256 supply = totalSupply(); + + // 1:1 exchange rate on the first stake. + // Use totalSupply to see if this call is made before boostrap call, not totalAssets + if (supply == 0) { + return shares; + } + + return Math.mulDiv(shares, totalAssets, supply, rounding); + } + + function setTotalAssets(uint256 _totalAssets) external { + totalAssets = _totalAssets; + } +} diff --git a/test/unit/ynETHWithdrawalQueueManager_unit.t.sol b/test/unit/ynETHWithdrawalQueueManager_unit.t.sol new file mode 100644 index 000000000..b23ec69ab --- /dev/null +++ b/test/unit/ynETHWithdrawalQueueManager_unit.t.sol @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.24; + +import {IRedemptionAssetsVault} from "../../src/interfaces/IRedemptionAssetsVault.sol"; +import {IynETH} from "../../src/interfaces/IynETH.sol"; + +import {WithdrawalQueueManager, IWithdrawalQueueManager} from "../../src/WithdrawalQueueManager.sol"; +import {ynETHRedemptionAssetsVault} from "../../src/ynETHRedemptionAssetsVault.sol"; + +import {MockRedeemableYnETH} from "./mocks/MockRedeemableYnETH.sol"; + +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20Errors} from "lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol"; + +import "forge-std/Test.sol"; + +contract ynETHWithdrawalQueueManagerTest is Test { + + error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); + + address public admin = address(0x65432); + address public withdrawalQueueAdmin = address(0x76543); + address public user = address(0x123456); + address public feeReceiver = address(0xabc); + address public redemptionAssetWithdrawer = address(0xdef); + address public requestFinalizer = address(0xabdef1234567); + + WithdrawalQueueManager public manager; + MockRedeemableYnETH public redeemableAsset; + ynETHRedemptionAssetsVault public redemptionAssetsVault; + + // ============================================================================================ + // Setup + // ============================================================================================ + + function setUp() public { + + redeemableAsset = new MockRedeemableYnETH(); + + ynETHRedemptionAssetsVault redemptionAssetsVaultImplementation = new ynETHRedemptionAssetsVault(); + TransparentUpgradeableProxy redemptionAssetsVaultProxy = new TransparentUpgradeableProxy( + address(redemptionAssetsVaultImplementation), + admin, // admin of the proxy + "" + ); + redemptionAssetsVault = ynETHRedemptionAssetsVault(payable(address(redemptionAssetsVaultProxy))); + + WithdrawalQueueManager.Init memory init = WithdrawalQueueManager.Init({ + name: "ynETH Withdrawal", + symbol: "ynETHW", + redeemableAsset: redeemableAsset, + redemptionAssetsVault: IRedemptionAssetsVault((address(redemptionAssetsVault))), + redemptionAssetWithdrawer: redemptionAssetWithdrawer, + admin: admin, + withdrawalQueueAdmin: withdrawalQueueAdmin, + requestFinalizer: requestFinalizer, + withdrawalFee: 10000, // 1% + feeReceiver: feeReceiver + }); + + bytes memory initData = abi.encodeWithSelector(WithdrawalQueueManager.initialize.selector, init); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(new WithdrawalQueueManager()), + admin, // admin of the proxy + initData + ); + + manager = WithdrawalQueueManager(payable(address(proxy))); + + ynETHRedemptionAssetsVault.Init memory vaultInit = ynETHRedemptionAssetsVault.Init({ + admin: admin, + redeemer: address(manager), + ynETH: IynETH(address(redeemableAsset)) + }); + redemptionAssetsVault.initialize(vaultInit); + + uint256 initialMintAmount = 1_000_000 ether; + redeemableAsset.mint(user, initialMintAmount); + + // rate is 1:1 + redeemableAsset.setTotalAssets(initialMintAmount); + } + + function finalizeRequest(uint256 tokenId) internal returns (uint256) { + vm.prank(requestFinalizer); + return manager.finalizeRequestsUpToIndex(tokenId + 1); + } + + function calculateNetEthAndFee( + uint256 amount, + uint256 redemptionRate, + uint256 feePercentage + ) public view returns (uint256 netEthAmount, uint256 feeAmount) { + uint256 FEE_PRECISION = manager.FEE_PRECISION(); + uint256 ethAmount = amount * redemptionRate / 1e18; + feeAmount = (ethAmount * feePercentage) / FEE_PRECISION; + netEthAmount = ethAmount - feeAmount; + return (netEthAmount, feeAmount); + } + + // ============================================================================================ + // withdrawalQueueManager.requestWithdrawal + // ============================================================================================ + + function testRequestWithdrawal(uint256 _amount) public { + vm.assume(_amount > 0 && _amount < 10_000 ether); + + uint256 _pendingRequestedRedemptionAmountBefore = manager.pendingRequestedRedemptionAmount(); + + vm.startPrank(user); + redeemableAsset.approve(address(manager), _amount); + manager.requestWithdrawal(_amount); + vm.stopPrank(); + + IWithdrawalQueueManager.WithdrawalRequest memory _withdrawalRequest = manager.withdrawalRequest(0); + assertEq(_withdrawalRequest.amount, _amount, "testRequestWithdrawal: E0"); + assertEq(_withdrawalRequest.feeAtRequestTime, manager.withdrawalFee(), "testRequestWithdrawal: E1"); + assertEq(_withdrawalRequest.redemptionRateAtRequestTime, redemptionAssetsVault.redemptionRate(), "testRequestWithdrawal: E2"); + assertEq(_withdrawalRequest.creationTimestamp, block.timestamp, "testRequestWithdrawal: E3"); + assertEq(_withdrawalRequest.processed, false, "testRequestWithdrawal: E4"); + assertEq(manager.balanceOf(user), 1, "testRequestWithdrawal: E5"); + assertEq(manager.pendingRequestedRedemptionAmount(), _pendingRequestedRedemptionAmountBefore + _amount, "testRequestWithdrawal: E6"); + } + + function testRequestWithdrawalWithZeroAmount() public { + uint256 amount = 0; + vm.prank(user); + redeemableAsset.approve(address(manager), amount); + vm.prank(user); + vm.expectRevert(WithdrawalQueueManager.AmountMustBeGreaterThanZero.selector); + manager.requestWithdrawal(amount); + } + + function testRequestWithdrawalWithMaxUintAmount() public { + uint256 maxUintAmount = type(uint256).max; + vm.prank(user); + redeemableAsset.approve(address(manager), maxUintAmount); + uint256 userBalance = redeemableAsset.balanceOf(user); + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, user, userBalance, maxUintAmount)); + manager.requestWithdrawal(maxUintAmount); + } + + function testRequestWithdrawalWithInsufficientApproval() public { + uint256 amount = 10 ether; + uint256 approvedAmount = 1 ether; // Less than the requested amount + vm.prank(user); + redeemableAsset.approve(address(manager), approvedAmount); + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(manager), approvedAmount, amount)); + manager.requestWithdrawal(amount); + } + + function testRequestWithdrawalWithExactZeroApproval() public { + uint256 amount = 10 ether; + vm.prank(user); + redeemableAsset.approve(address(manager), 0); + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(manager), 0, amount)); + manager.requestWithdrawal(amount); + } + + // ============================================================================================ + // withdrawalQueueManager.claimWithdrawal + // ============================================================================================ + + function testClaimWithdrawalSuccesfully(uint256 _amount) public { + vm.assume(_amount > 0 && _amount < 10_000 ether); + + vm.deal(address(redemptionAssetsVault), _amount); + + vm.startPrank(user); + redeemableAsset.approve(address(manager), _amount); + uint256 tokenId = manager.requestWithdrawal(_amount); + vm.stopPrank(); + + uint256 _redemptionRateAtRequestTime = redemptionAssetsVault.redemptionRate(); + + uint256 finalizationId = finalizeRequest(tokenId); + + uint256 _userBalanceBefore = user.balance; + uint256 _vaultBalanceBefore = address(redemptionAssetsVault).balance; + vm.prank(user); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, receiver: user , finalizationId: finalizationId } + ) + ); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertTrue(request.processed, "testClaimWithdrawal: E0"); + assertEq(request.amount, _amount, "testClaimWithdrawal: E1"); + assertEq(request.feeAtRequestTime, manager.withdrawalFee(), "testClaimWithdrawal: E2"); + assertEq(request.redemptionRateAtRequestTime, _redemptionRateAtRequestTime, "testClaimWithdrawal: E3"); + + uint256 expectedFeeAmount = (_amount * request.feeAtRequestTime) / manager.FEE_PRECISION(); + uint256 expectedNetEthAmount = (_amount * request.redemptionRateAtRequestTime) / 1e18 - expectedFeeAmount; + assertEq(user.balance, expectedNetEthAmount, "testClaimWithdrawal: E5"); + assertEq(redeemableAsset.balanceOf(address(manager)), 0, "testClaimWithdrawal: E6"); + assertEq(feeReceiver.balance, expectedFeeAmount, "testClaimWithdrawal: E7"); + assertApproxEqAbs(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, 1000, "testClaimWithdrawal: E8"); + assertEq(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, "testClaimWithdrawal: E8"); + assertEq(manager.balanceOf(user), 0, "testClaimWithdrawal: E9"); + assertEq(user.balance - _userBalanceBefore, expectedNetEthAmount, "testClaimWithdrawal: E10"); + } + + function testClaimWithdrawalRevertsWhenInsufficientVaultBalance() public { + uint256 amount = 1 ether; + vm.prank(user); + redeemableAsset.approve(address(manager), amount); + vm.prank(user); + uint256 tokenId = manager.requestWithdrawal(amount); + + uint256 finalizationId = finalizeRequest(tokenId); + + // Ensure vault has insufficient balance + uint256 insufficientAmount = amount - 1; + (bool success, ) = address(redemptionAssetsVault).call{value: insufficientAmount}(""); + require(success, "Ether transfer failed"); + + vm.prank(user); + vm.expectRevert( + abi.encodeWithSelector( + WithdrawalQueueManager.InsufficientBalance.selector, + insufficientAmount, + amount + ) + ); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: 0, receiver: user , finalizationId: finalizationId } + ) + ); + + } + + function testClaimOneWithdrawalNotFinalized() public { + uint256 amount = 1 ether; + vm.prank(user); + redeemableAsset.approve(address(manager), amount); + vm.prank(user); + uint256 tokenId = manager.requestWithdrawal(amount); + + // Attempt to claim before time is up + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.InvalidFinalizationId.selector, 0)); + vm.prank(user); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, receiver: user , finalizationId: 0 } + ) + ); + } + + function testClaimWithdrawalForNonExistentTokenId() public { + uint256 nonExistentTokenId = 9999; // Assuming this tokenId does not exist + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.CallerNotOwnerNorApproved.selector, nonExistentTokenId, user)); + + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: nonExistentTokenId, receiver: user , finalizationId: 0 } + ) + ); + } + + function testClaimWithdrawalForAlreadyProcessedWithdrawal() public { + uint256 amount = 10 ether; // Example amount to process withdrawal + uint256 availableRedemptionAmount = 100 ether; + + // Simulate user requesting a withdrawal + vm.prank(user); + redeemableAsset.approve(address(manager), amount); + vm.prank(user); + uint256 tokenId = manager.requestWithdrawal(amount); + + uint256 finalizationId = finalizeRequest(tokenId); + + // Send exact Ether to vault + (bool success, ) = address(redemptionAssetsVault).call{value: availableRedemptionAmount}(""); + require(success, "Ether transfer failed"); + + // Attempt to claim the withdrawal + vm.prank(user); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, receiver: user , finalizationId: finalizationId } + ) + ); + + // Attempt to claim the withdrawal again to ensure it cannot be processed twice + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.CallerNotOwnerNorApproved.selector, tokenId, user)); + vm.prank(user); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, receiver: user , finalizationId: finalizationId } + ) + ); + } + + function testtestClaimWithdrawalNotOwner() public { + uint256 tokenId = 1; // Assuming this tokenId exists and is owned by another user + address notOwner = vm.addr(9999); // An arbitrary address that is not the owner + + + vm.prank(notOwner); + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.CallerNotOwnerNorApproved.selector, tokenId, notOwner)); + manager.claimWithdrawal( + IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, receiver: notOwner, finalizationId: 0 } + ) + ); + } + + // ============================================================================================ + // withdrawalQueueManager.claimWithdrawals + // ============================================================================================ + + function testclaimWithdrawals(uint256 _amount) public { + vm.assume(_amount > 0 && _amount < 10_000 ether); + + vm.deal(address(redemptionAssetsVault), _amount); + + vm.startPrank(user); + redeemableAsset.approve(address(manager), _amount); + uint256 tokenId = manager.requestWithdrawal(_amount); + vm.stopPrank(); + + uint256 _redemptionRateAtRequestTime = redemptionAssetsVault.redemptionRate(); + + uint256 finalizationId = finalizeRequest(tokenId); + + uint256 _userBalanceBefore = user.balance; + uint256 _vaultBalanceBefore = address(redemptionAssetsVault).balance; + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + finalizationId: finalizationId, + receiver: user + }); + + vm.prank(user); + manager.claimWithdrawals(claims); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertTrue(request.processed, "testclaimWithdrawals: E0"); + assertEq(request.amount, _amount, "testclaimWithdrawals: E1"); + assertEq(request.feeAtRequestTime, manager.withdrawalFee(), "testclaimWithdrawals: E2"); + assertEq(request.redemptionRateAtRequestTime, _redemptionRateAtRequestTime, "testclaimWithdrawals: E3"); + + uint256 expectedFeeAmount = (_amount * request.feeAtRequestTime) / manager.FEE_PRECISION(); + uint256 expectedNetEthAmount = (_amount * request.redemptionRateAtRequestTime) / 1e18 - expectedFeeAmount; + assertEq(user.balance, expectedNetEthAmount, "testclaimWithdrawals: E5"); + assertEq(redeemableAsset.balanceOf(address(manager)), 0, "testclaimWithdrawals: E6"); + assertEq(feeReceiver.balance, expectedFeeAmount, "testclaimWithdrawals: E7"); + assertApproxEqAbs(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, 1000, "testclaimWithdrawals: E8"); + assertEq(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, "testclaimWithdrawals: E8"); + assertEq(manager.balanceOf(user), 0, "testclaimWithdrawals: E9"); + assertEq(user.balance - _userBalanceBefore, expectedNetEthAmount, "testclaimWithdrawals: E10"); + } + + function testClaimWithdrawalsWithIncreasedRedemptionRate() public { + uint256 initialAmount = 100 ether; + + // Deal ETH to redemptionAssetsVault + uint256 vaultBalance = 1000 ether; // Set an arbitrary balance for the vault + vm.deal(address(redemptionAssetsVault), vaultBalance); + + // User requests withdrawal + vm.startPrank(user); + redeemableAsset.approve(address(manager), initialAmount); + uint256 tokenId = manager.requestWithdrawal(initialAmount); + vm.stopPrank(); + + uint256 initialRedemptionRate = redemptionAssetsVault.redemptionRate(); + assertEq(initialRedemptionRate, 1e18, "Initial redemption rate should be 1:1"); + + uint256 finalizationId = finalizeRequest(tokenId); + + // Increase total assets, which should increase the redemption rate + uint256 currentTotalAssets = redeemableAsset.totalAssets(); + uint256 increasedAmount = currentTotalAssets + (currentTotalAssets * 20 / 100); + redeemableAsset.setTotalAssets(increasedAmount); + + uint256 newRedemptionRate = redemptionAssetsVault.redemptionRate(); + assertGt(newRedemptionRate, initialRedemptionRate, "Redemption rate should have increased"); + + uint256 userBalanceBefore = user.balance; + + vm.prank(user); + IWithdrawalQueueManager.WithdrawalClaim[] memory claims = new IWithdrawalQueueManager.WithdrawalClaim[](1); + claims[0] = IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + receiver: user, + finalizationId: finalizationId + }); + manager.claimWithdrawals(claims); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertTrue(request.processed, "Request should be processed"); + + // Calculate expected amounts + uint256 expectedFeeAmount = (initialAmount * request.feeAtRequestTime) / manager.FEE_PRECISION(); + uint256 expectedNetEthAmount = (initialAmount * request.redemptionRateAtRequestTime) / 1e18 - expectedFeeAmount; + + // Verify that the user received the correct amount based on the initial redemption rate + assertEq(user.balance - userBalanceBefore, expectedNetEthAmount, "User should receive ETH based on initial redemption rate"); + + // Verify that the manager's balance of redeemable asset is zero + assertEq(redeemableAsset.balanceOf(address(manager)), 0, "Manager should have no redeemable assets left"); + + // Verify that the fee receiver received the correct fee + assertEq(feeReceiver.balance, expectedFeeAmount, "Fee receiver should receive correct fee amount"); + } + + function testClaimWithdrawalsWithDecreasedRedemptionRate() public { + + // Log the fee percentage + uint256 feePercentage = manager.withdrawalFee(); + + uint256 initialAmount = 100 ether; + + // Deal ETH to redemptionAssetsVault + uint256 vaultBalance = 1000 ether; + vm.deal(address(redemptionAssetsVault), vaultBalance); + + // User requests withdrawal + vm.startPrank(user); + redeemableAsset.approve(address(manager), initialAmount); + uint256 tokenId = manager.requestWithdrawal(initialAmount); + vm.stopPrank(); + + uint256 initialRedemptionRate = redemptionAssetsVault.redemptionRate(); + assertEq(initialRedemptionRate, 1e18, "Initial redemption rate should be 1:1"); + + + // Decrease total assets, which should decrease the redemption rate + uint256 currentTotalAssets = redeemableAsset.totalAssets(); + uint256 decreasedAmount = currentTotalAssets - (currentTotalAssets * 20 / 100); + redeemableAsset.setTotalAssets(decreasedAmount); + + uint256 newRedemptionRate = redemptionAssetsVault.redemptionRate(); + assertLt(newRedemptionRate, initialRedemptionRate, "Redemption rate should have decreased"); + + // finalize request at the new rate + uint256 finalizationId = finalizeRequest(tokenId); + { + // Assert finalization fields + IWithdrawalQueueManager.Finalization memory finalization = manager.getFinalization(finalizationId); + assertEq(finalization.startIndex, 0, "Start index should be 0"); + assertEq(finalization.endIndex, tokenId + 1, "End index should be tokenId + 1"); + assertEq(finalization.redemptionRate, newRedemptionRate, "Finalization redemption rate should match new rate"); + } + + uint256 userBalanceBefore = user.balance; + + vm.prank(user); + manager.claimWithdrawal(IWithdrawalQueueManager.WithdrawalClaim({ + tokenId: tokenId, + finalizationId: finalizationId, + receiver: user + })); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertTrue(request.processed, "Request should be processed"); + + // Calculate expected amounts + (uint256 expectedNetEthAmount, uint256 expectedFeeAmount) = calculateNetEthAndFee(initialAmount,newRedemptionRate, feePercentage); + + // Verify that the user received the correct amount based on the new (lower) redemption rate + assertEq(user.balance - userBalanceBefore, expectedNetEthAmount, "User should receive ETH based on new (lower) redemption rate"); + + // Verify that the manager's balance of redeemable asset is zero + assertEq(redeemableAsset.balanceOf(address(manager)), 0, "Manager should have no redeemable assets left"); + + // Verify that the fee receiver received the correct fee + assertEq(feeReceiver.balance, expectedFeeAmount, "Fee receiver should receive correct fee amount"); + } + + + function testclaimWithdrawalWithComputedFinalizationId( + uint256 _amount + ) public { + vm.assume(_amount > 0 && _amount < 10_000 ether); + + vm.deal(address(redemptionAssetsVault), _amount); + + uint256 extraWithdrawalsBefore = 3; + { + address anotherUser = address(0x9876); + vm.deal(anotherUser, 100 ether); + redeemableAsset.mint(anotherUser, 100 ether); + // Increase total assets of redeemableAsset by 100 ether to maintain ratio + vm.prank(admin); + redeemableAsset.setTotalAssets(redeemableAsset.totalAssets() + 100 ether); + + // Perform 3 withdrawal requests of 1 ether each for anotherUser + for (uint256 i = 0; i < extraWithdrawalsBefore; i++) { + vm.startPrank(anotherUser); + redeemableAsset.approve(address(manager), 1 ether); + manager.requestWithdrawal(1 ether); + vm.stopPrank(); + + // finalize immediately + finalizeRequest(i); + } + } + + vm.startPrank(user); + redeemableAsset.approve(address(manager), _amount); + uint256 tokenId = manager.requestWithdrawal(_amount); + vm.stopPrank(); + + uint256 _redemptionRateAtRequestTime = redemptionAssetsVault.redemptionRate(); + + finalizeRequest(tokenId); + + + uint256 extraWithdrawalsAfter = 2; + { + address anotherUser = address(0x9876); + vm.deal(anotherUser, 100 ether); + redeemableAsset.mint(anotherUser, 100 ether); + // Increase total assets of redeemableAsset by 100 ether to maintain ratio + vm.prank(admin); + redeemableAsset.setTotalAssets(redeemableAsset.totalAssets() + 100 ether); + + // Perform 3 withdrawal requests of 1 ether each for anotherUser + for (uint256 i = 0; i < extraWithdrawalsAfter; i++) { + vm.startPrank(anotherUser); + redeemableAsset.approve(address(manager), 1 ether); + manager.requestWithdrawal(1 ether); + vm.stopPrank(); + + // finalize immediately + finalizeRequest(i + tokenId + 1); + } + } + + uint256 _userBalanceBefore = user.balance; + uint256 _vaultBalanceBefore = address(redemptionAssetsVault).balance; + + vm.prank(user); + manager.claimWithdrawal(tokenId, user); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertTrue(request.processed, "testclaimWithdrawals: E0"); + assertEq(request.amount, _amount, "testclaimWithdrawals: E1"); + assertEq(request.feeAtRequestTime, manager.withdrawalFee(), "testclaimWithdrawals: E2"); + assertEq(request.redemptionRateAtRequestTime, _redemptionRateAtRequestTime, "testclaimWithdrawals: E3"); + + uint256 expectedFeeAmount = (_amount * request.feeAtRequestTime) / manager.FEE_PRECISION(); + uint256 expectedNetEthAmount = (_amount * request.redemptionRateAtRequestTime) / 1e18 - expectedFeeAmount; + assertEq(user.balance, expectedNetEthAmount, "testclaimWithdrawals: E5"); + assertEq(redeemableAsset.balanceOf(address(manager)), (extraWithdrawalsBefore + extraWithdrawalsAfter) * 1 ether, "testclaimWithdrawals: E6"); + assertEq(feeReceiver.balance, expectedFeeAmount, "testclaimWithdrawals: E7"); + assertApproxEqAbs(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, 1000, "testclaimWithdrawals: E8"); + assertEq(address(redemptionAssetsVault).balance, _vaultBalanceBefore - _amount, "testclaimWithdrawals: E8"); + assertEq(manager.balanceOf(user), 0, "testclaimWithdrawals: E9"); + assertEq(user.balance - _userBalanceBefore, expectedNetEthAmount, "testclaimWithdrawals: E10"); + } + + // ============================================================================================ + // withdrawalQueueManager.surplusRedemptionAssets + // ============================================================================================ + + function testSurplusRedemptionAssets() public { + assertEq(redemptionAssetsVault.availableRedemptionAssets(), 0, "testSurplusRedemptionAssets: E0"); + assertEq(manager.pendingRequestedRedemptionAmount(), 0, "testSurplusRedemptionAssets: E1"); + assertEq(manager.surplusRedemptionAssets(), 0, "testSurplusRedemptionAssets: E2"); + + vm.deal(address(redemptionAssetsVault), 100 ether); + assertEq(redemptionAssetsVault.availableRedemptionAssets(), 100 ether, "testSurplusRedemptionAssets: E3"); + assertEq(manager.pendingRequestedRedemptionAmount(), 0, "testSurplusRedemptionAssets: E4"); + assertEq(manager.surplusRedemptionAssets(), 100 ether, "testSurplusRedemptionAssets: E5"); + } + + // ============================================================================================ + // withdrawalQueueManager.surplusRedemptionAssets + // ============================================================================================ + + function testDeficitRedemptionAssets() public { + assertEq(redemptionAssetsVault.availableRedemptionAssets(), 0, "testDeficitRedemptionAssets: E0"); + assertEq(manager.pendingRequestedRedemptionAmount(), 0, "testDeficitRedemptionAssets: E1"); + assertEq(manager.deficitRedemptionAssets(), 0, "testDeficitRedemptionAssets: E2"); + + vm.deal(address(redemptionAssetsVault), 100 ether); + assertEq(redemptionAssetsVault.availableRedemptionAssets(), 100 ether, "testDeficitRedemptionAssets: E3"); + assertEq(manager.pendingRequestedRedemptionAmount(), 0, "testDeficitRedemptionAssets: E4"); + assertEq(manager.deficitRedemptionAssets(), 0, "testDeficitRedemptionAssets: E5"); + + uint256 amount = 50 ether; + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + manager.requestWithdrawal(amount); + vm.stopPrank(); + assertEq(manager.pendingRequestedRedemptionAmount(), amount, "testDeficitRedemptionAssets: E6"); + assertEq(manager.deficitRedemptionAssets(), 0, "testDeficitRedemptionAssets: E7"); + + amount = 100 ether; + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + manager.requestWithdrawal(amount); + vm.stopPrank(); + + assertEq(manager.pendingRequestedRedemptionAmount(), 150 ether, "testDeficitRedemptionAssets: E8"); + assertEq(manager.deficitRedemptionAssets(), 50 ether, "testDeficitRedemptionAssets: E9"); + } + + // ============================================================================================ + // withdrawalQueueManager.withdrawSurplusRedemptionAssets + // ============================================================================================ + + function testWithdrawSurplusRedemptionAssets() public { + uint256 amount = 100 ether; + vm.deal(address(redemptionAssetsVault), amount); + assertEq(manager.surplusRedemptionAssets(), amount, "testWithdrawSurplusRedemptionAssets: E0"); + + uint256 _ynethBalanceBefore = address(redeemableAsset).balance; + + vm.prank(redemptionAssetWithdrawer); + manager.withdrawSurplusRedemptionAssets(amount); + + assertEq(manager.surplusRedemptionAssets(), 0, "testWithdrawSurplusRedemptionAssets: E1"); + assertEq(address(redeemableAsset).balance - _ynethBalanceBefore, amount, "testWithdrawSurplusRedemptionAssets: E2"); + } + + function testWithdrawSurplusRedemptionAssetsAmountExceedsSurplus() public { + uint256 amount = 100 ether; + vm.deal(address(redemptionAssetsVault), amount); + assertEq(manager.surplusRedemptionAssets(), amount, "testWithdrawSurplusRedemptionAssetsAmountExceedsSurplus: E0"); + + uint256 _ynethBalanceBefore = address(redeemableAsset).balance; + + uint256 amountExceedingSurplus = amount + 1; + vm.prank(redemptionAssetWithdrawer); + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.AmountExceedsSurplus.selector, amountExceedingSurplus, amount)); + manager.withdrawSurplusRedemptionAssets(amountExceedingSurplus); + + assertEq(manager.surplusRedemptionAssets(), amount, "testWithdrawSurplusRedemptionAssetsAmountExceedsSurplus: E1"); + assertEq(address(redeemableAsset).balance - _ynethBalanceBefore, 0, "testWithdrawSurplusRedemptionAssetsAmountExceedsSurplus: E2"); + } + + // ============================================================================================ + // withdrawalQueueManager.withdrawalRequestIsFinalized / withdrawalQueueManager.isFinalized + // ============================================================================================ + + function testWithdrawalRequestIsFinalized() public { + uint256 amount = 100 ether; + vm.deal(address(redemptionAssetsVault), amount); + assertEq(manager.surplusRedemptionAssets(), amount, "testWithdrawalRequestIsFinalized: E0"); + + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + uint256 tokenId = manager.requestWithdrawal(amount); + vm.stopPrank(); + + assertEq(manager.withdrawalRequestIsFinalized(tokenId), false, "testWithdrawalRequestIsFinalized: E1"); + + finalizeRequest(tokenId); + assertEq(manager.withdrawalRequestIsFinalized(tokenId), true, "testWithdrawalRequestIsFinalized: E3"); + } + + // ============================================================================================ + // withdrawalQueueManager.withdrawalRequest + // ============================================================================================ + + function testWithdrawalRequest() public { + uint256 amount = 100 ether; + vm.deal(address(redemptionAssetsVault), amount); + assertEq(manager.surplusRedemptionAssets(), amount, "testWithdrawalRequest: E0"); + + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + uint256 tokenId = manager.requestWithdrawal(amount); + vm.stopPrank(); + + IWithdrawalQueueManager.WithdrawalRequest memory request = manager.withdrawalRequest(tokenId); + assertEq(request.amount, amount, "testWithdrawalRequest: E1"); + assertEq(request.feeAtRequestTime, manager.withdrawalFee(), "testWithdrawalRequest: E2"); + assertEq(request.redemptionRateAtRequestTime, redemptionAssetsVault.redemptionRate(), "testWithdrawalRequest: E3"); + assertEq(request.creationTimestamp, block.timestamp, "testWithdrawalRequest: E4"); + assertEq(request.processed, false, "testWithdrawalRequest: E5"); + } + + // ============================================================================================ + // withdrawalQueueManager.supportsInterface + // ============================================================================================ + + function testSupportsInterface() public { + bytes4 interfaceId = 0x80ac58cd; // IERC721 + assertEq(manager.supportsInterface(interfaceId), true, "testSupportsInterface: E0"); + } + + // ============================================================================================ + // withdrawalQueueManager.setWithdrawalFee + // ============================================================================================ + + function testSetWithdrawalFee(uint256 _feePercentage) public { + vm.assume(_feePercentage <= manager.FEE_PRECISION()); + + assertEq(manager.withdrawalFee(), 10000, "testSetWithdrawalFee: E0"); // from setUp + + vm.prank(withdrawalQueueAdmin); + manager.setWithdrawalFee(_feePercentage); + + assertEq(manager.withdrawalFee(), _feePercentage, "testSetWithdrawalFee: E1"); + } + + function testSetWithdrawalFeeFeePercentageExceedsLimit() public { + uint256 _feePercentage = manager.FEE_PRECISION() + 1; + vm.prank(withdrawalQueueAdmin); + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.FeePercentageExceedsLimit.selector)); + manager.setWithdrawalFee(_feePercentage); + } + + function testSetWithdrawalFeeWrongCaller() public { + uint256 _feePercentage = 1; + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), manager.WITHDRAWAL_QUEUE_ADMIN_ROLE())); + manager.setWithdrawalFee(_feePercentage); + } + + // ============================================================================================ + // withdrawalQueueManager.setFeeReceiver + // ============================================================================================ + + function testSetFeeReceiver() public { + address newFeeReceiver = vm.addr(9999); + assertEq(manager.feeReceiver(), feeReceiver, "testSetFeeReceiver: E0"); // from setUp + + vm.prank(withdrawalQueueAdmin); + manager.setFeeReceiver(newFeeReceiver); + + assertEq(manager.feeReceiver(), newFeeReceiver, "testSetFeeReceiver: E1"); + } + + function testSetFeeReceiverZeroAddress() public { + address zeroAddress = address(0); + vm.prank(withdrawalQueueAdmin); + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.ZeroAddress.selector)); + manager.setFeeReceiver(zeroAddress); + } + + function testSetFeeReceiverWrongCaller() public { + address newFeeReceiver = vm.addr(9999); + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), manager.WITHDRAWAL_QUEUE_ADMIN_ROLE())); + manager.setFeeReceiver(newFeeReceiver); + } + + // ============================================================================================ + // ynETHRedemptionAssetsVault.finalizeRequestsUpToIndex + // ============================================================================================ + + function testFinalizeRequestsUpToIndexSuccessfullyForMultipleRequests(uint256 _amount, uint256 requestIncrease) public { + + vm.assume(_amount > 0 && _amount < 10_000 ether); + vm.assume(requestIncrease >= 0 && requestIncrease < 1000 ether); + uint256 requestIndex = 5; + + uint256[] memory requestedAmounts = new uint256[](requestIndex); + for (uint256 i = 0; i < requestIndex; i++) { + requestedAmounts[i] = _amount + requestIncrease * i; + } + + // requesting withdrawals to the vault + for (uint256 i = 0; i < requestIndex; i++) { + vm.startPrank(user); + redeemableAsset.approve(address(manager), requestedAmounts[i]); + manager.requestWithdrawal(requestedAmounts[i]); + vm.stopPrank(); + + vm.deal(address(redemptionAssetsVault), requestedAmounts[i]); + } + + // Finalize requests up to the specified index + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + + // Check if the requests up to the specified index are finalized + for (uint256 i = 0; i < requestIndex; i++) { + bool isFinalized = manager.withdrawalRequestIsFinalized(i); + assertTrue(isFinalized, string.concat("Request ", vm.toString(i), " should be finalized")); + } + } + + function testFinalizeRequestsUpToIndexWrongCaller() public { + uint256 requestIndex = 3; + vm.expectRevert( + abi.encodeWithSelector( + AccessControlUnauthorizedAccount.selector, + address(this), + manager.REQUEST_FINALIZER_ROLE() + ) + ); + manager.finalizeRequestsUpToIndex(requestIndex); + } + + function testFinalizeRequestsUpToIndexWithInvalidIndex() public { + uint256 requestIndex = 999; // Assuming an index that is out of bounds + vm.expectRevert( + abi.encodeWithSelector( + WithdrawalQueueManager.IndexExceedsTokenCount.selector, + requestIndex, + manager._tokenIdCounter() + ) + ); + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + } + + function testFinalizeRequestsNotAdvanced() public { + + // Make a withdrawal request + uint256 amount = 1 ether; + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + manager.requestWithdrawal(amount); + vm.stopPrank(); + + // Ensure the request was made successfully + assertEq(manager._tokenIdCounter(), 1, "Token counter should be incremented"); + + // Finalize the request + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(1); + + // Check if the request is finalized + bool isFinalized = manager.withdrawalRequestIsFinalized(0); + assertTrue(isFinalized, "Request should be finalized"); + + // Verify lastFinalizedIndex + assertEq(manager.lastFinalizedIndex(), 1, "lastFinalizedIndex should be updated"); + + + uint256 initialFinalizedIndex = manager.lastFinalizedIndex(); + uint256 requestIndex = initialFinalizedIndex; // Same as the last finalized index to trigger IndexNotAdvanced error + + vm.expectRevert( + abi.encodeWithSelector( + WithdrawalQueueManager.IndexNotAdvanced.selector, + requestIndex, + initialFinalizedIndex + ) + ); + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + } + + function testFinalizeRequestsUpToIndexWithPreExistingRequest() public { + // Setup: Create a pre-existing request + uint256 amount = 1 ether; + vm.startPrank(user); + redeemableAsset.approve(address(manager), amount); + uint256 tokenId = manager.requestWithdrawal(amount); + vm.stopPrank(); + + // Attempt to finalize up to the request index including the pre-existing request + uint256 requestIndex = tokenId + 1; // Index 0 is the pre-existing request + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + + // Check if the pre-existing request is finalized + bool isFinalized = manager.withdrawalRequestIsFinalized(0); + assertTrue(isFinalized, "Pre-existing request should be finalized"); + + vm.expectRevert( + abi.encodeWithSelector( + WithdrawalQueueManager.IndexNotAdvanced.selector, + requestIndex, + requestIndex + ) + ); + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + } + + function testFinalizeRequestsUpToIndexWithNoExistingRequests() public { + // Check initial state + assertEq(manager.lastFinalizedIndex(), 0, "Initial lastFinalizedIndex should be 0"); + assertEq(manager._tokenIdCounter(), 0, "Initial _tokenIdCounter should be 0"); + + uint256 requestIndex = 0; + // Attempt to finalize (should revert) + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.IndexNotAdvanced.selector, requestIndex, requestIndex)); + vm.prank(requestFinalizer); + manager.finalizeRequestsUpToIndex(requestIndex); + } + + // ============================================================================================ + // ynETHRedemptionAssetsVault.findFinalizationForTokenId + // ============================================================================================ + + function testFindFinalizationForTokenId() public { + // Create 12 withdrawal requests + uint256[] memory amounts = new uint256[](12); + uint256[] memory tokenIds = new uint256[](12); + for (uint256 i = 0; i < 12; i++) { + amounts[i] = 1 ether * (i + 1); + vm.startPrank(user); + redeemableAsset.approve(address(manager), amounts[i]); + tokenIds[i] = manager.requestWithdrawal(amounts[i]); + vm.stopPrank(); + } + + // Finalize requests in 5 batches + vm.startPrank(requestFinalizer); + manager.finalizeRequestsUpToIndex(2); // Finalization 0: tokenIds 0-1 + manager.finalizeRequestsUpToIndex(5); // Finalization 1: tokenIds 2-4 + manager.finalizeRequestsUpToIndex(8); // Finalization 2: tokenIds 5-7 + manager.finalizeRequestsUpToIndex(9); // Finalization 3: tokenIds 8-8 + manager.finalizeRequestsUpToIndex(12); // Finalization 4: tokenIds 9-11 + vm.stopPrank(); + + // Test finding finalization for each token ID + uint256[] memory expectedFinalizationIds = new uint256[](12); + expectedFinalizationIds[0] = 0; + expectedFinalizationIds[1] = 0; + expectedFinalizationIds[2] = 1; + expectedFinalizationIds[3] = 1; + expectedFinalizationIds[4] = 1; + expectedFinalizationIds[5] = 2; + expectedFinalizationIds[6] = 2; + expectedFinalizationIds[7] = 2; + expectedFinalizationIds[8] = 3; + expectedFinalizationIds[9] = 4; + expectedFinalizationIds[10] = 4; + expectedFinalizationIds[11] = 4; + for (uint256 i = 0; i < 12; i++) { + uint256 actualFinalizationId = manager.findFinalizationForTokenId(tokenIds[i]); + assertEq(actualFinalizationId, expectedFinalizationIds[i], string(abi.encodePacked("Incorrect finalization ID for token ", vm.toString(tokenIds[i])))); + } + + // Test finding finalization for non-existent token ID + uint256 nonExistentTokenId = 100; + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.NotFinalized.selector, nonExistentTokenId)); + manager.findFinalizationForTokenId(nonExistentTokenId); + } + + function testFindFinalizationForTokenIdWithOneFinalization() public { + // Create 3 withdrawal requests + uint256[] memory amounts = new uint256[](3); + uint256[] memory tokenIds = new uint256[](3); + for (uint256 i = 0; i < 3; i++) { + amounts[i] = 1 ether * (i + 1); + vm.startPrank(user); + redeemableAsset.approve(address(manager), amounts[i]); + tokenIds[i] = manager.requestWithdrawal(amounts[i]); + vm.stopPrank(); + } + + // Finalize all requests in one batch + vm.startPrank(requestFinalizer); + manager.finalizeRequestsUpToIndex(3); // Finalization 0: tokenIds 0-2 + vm.stopPrank(); + + // Test finding finalization for each token ID + for (uint256 i = 0; i < 3; i++) { + uint256 actualFinalizationId = manager.findFinalizationForTokenId(tokenIds[i]); + assertEq(actualFinalizationId, 0, string(abi.encodePacked("Incorrect finalization ID for token ", vm.toString(tokenIds[i])))); + } + + // Test finding finalization for non-existent token ID + uint256 nonExistentTokenId = 100; + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.NotFinalized.selector, nonExistentTokenId)); + manager.findFinalizationForTokenId(nonExistentTokenId); + } + + function testFindFinalizationForTokenIdWithNoFinalizations() public { + // Create a withdrawal request + vm.startPrank(user); + redeemableAsset.approve(address(manager), 1 ether); + uint256 tokenId = manager.requestWithdrawal(1 ether); + vm.stopPrank(); + + // Attempt to find finalization for the token ID without any finalizations + vm.expectRevert(abi.encodeWithSelector(WithdrawalQueueManager.NotFinalized.selector, tokenId)); + manager.findFinalizationForTokenId(tokenId); + } + + // ============================================================================================ + // ynETHRedemptionAssetsVault.pause + // ============================================================================================ + + function testPause() public { + assertEq(redemptionAssetsVault.paused(), false, "testPause: E0"); + + vm.prank(admin); + redemptionAssetsVault.pause(); + + assertEq(redemptionAssetsVault.paused(), true, "testPause: E1"); + + vm.expectRevert(abi.encodeWithSelector(ynETHRedemptionAssetsVault.ContractPaused.selector)); + vm.prank(address(manager)); + redemptionAssetsVault.transferRedemptionAssets(user, 1 ether, ""); + + vm.expectRevert(abi.encodeWithSelector(ynETHRedemptionAssetsVault.ContractPaused.selector)); + vm.prank(address(manager)); + redemptionAssetsVault.withdrawRedemptionAssets(1 ether); + } + + function testPauseWrongCaller() public { + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), redemptionAssetsVault.PAUSER_ROLE())); + redemptionAssetsVault.pause(); + } + + function testUnpause() public { + vm.prank(admin); + redemptionAssetsVault.pause(); + assertEq(redemptionAssetsVault.paused(), true, "testUnpause: E0"); + + vm.prank(admin); + redemptionAssetsVault.unpause(); + assertEq(redemptionAssetsVault.paused(), false, "testUnpause: E1"); + } + + function testUnpauseWrongCaller() public { + vm.expectRevert(abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, address(this), redemptionAssetsVault.UNPAUSER_ROLE())); + redemptionAssetsVault.unpause(); + } + + // ============================================================================================ + // withdrawalRequestsForOwner + // ============================================================================================ + + function testWithdrawalRequestsForOwner() public { + + address anotherUser = address(0x9876); + vm.deal(anotherUser, 100 ether); + + + // Mint initial ynETH tokens for users + redeemableAsset.mint(user, 100 ether); + redeemableAsset.mint(anotherUser, 100 ether); + + // Setup: Create multiple withdrawal requests for different users with varying amounts + uint256[] memory requestAmounts = new uint256[](4); + requestAmounts[0] = 1 ether; + requestAmounts[1] = 2.5 ether; + requestAmounts[2] = 0.75 ether; + requestAmounts[3] = 3.2 ether; + + + vm.startPrank(user); + redeemableAsset.approve(address(manager), 10 ether); // Approve enough for all transactions + uint256 request1 = manager.requestWithdrawal(requestAmounts[0]); + uint256 request2 = manager.requestWithdrawal(requestAmounts[1]); + uint256 request3 = manager.requestWithdrawal(requestAmounts[2]); + vm.stopPrank(); + // Store the request IDs in an array for easy access and verification + uint256[] memory requestIds = new uint256[](3); + requestIds[0] = request1; + requestIds[1] = request2; + requestIds[2] = request3; + + vm.startPrank(anotherUser); + redeemableAsset.approve(address(manager), requestAmounts[3]); + manager.requestWithdrawal(requestAmounts[3]); + vm.stopPrank(); + + // Test: Retrieve withdrawal requests for the main user + (uint256[] memory withdrawalIndexes, IWithdrawalQueueManager.WithdrawalRequest[] memory requests) = manager.withdrawalRequestsForOwner(user); + + // Assertions + assertEq(withdrawalIndexes.length, requestIds.length, "Incorrect number of withdrawal indexes"); + assertEq(requests.length, requestIds.length, "Incorrect number of withdrawal requests"); + + // Verify the content of each request + for (uint256 i = 0; i < requestIds.length; i++) { + assertEq(withdrawalIndexes[i], i, "Incorrect withdrawal index"); + assertEq(requests[i].amount, requestAmounts[i], "Incorrect withdrawal amount"); + assertTrue(requests[i].creationTimestamp > 0, "Invalid creation timestamp"); + assertFalse(requests[i].processed, "Request should not be claimed"); + } + + // Test: Retrieve withdrawal requests for the other user + (withdrawalIndexes, requests) = manager.withdrawalRequestsForOwner(anotherUser); + + // Assertions for the other user + assertEq(withdrawalIndexes.length, 1, "Incorrect number of withdrawal indexes for other user"); + assertEq(requests.length, 1, "Incorrect number of withdrawal requests for other user"); + assertEq(withdrawalIndexes[0], 3, "Incorrect withdrawal index for other user"); + assertEq(requests[0].amount, requestAmounts[3], "Incorrect withdrawal amount for other user"); + + assertTrue(requests[0].creationTimestamp > 0, "Invalid creation timestamp for other user"); + assertFalse(requests[0].processed, "Request should not be claimed for other user"); + + // Test: Retrieve withdrawal requests for an address with no requests + address noRequestUser = address(0x1111); + (withdrawalIndexes, requests) = manager.withdrawalRequestsForOwner(noRequestUser); + + // Assertions for address with no requests + assertEq(withdrawalIndexes.length, 0, "Should be no withdrawal indexes for address with no requests"); + assertEq(requests.length, 0, "Should be no withdrawal requests for address with no requests"); + } +} \ No newline at end of file diff --git a/test/utils/ProofUtils.sol b/test/utils/ProofUtils.sol deleted file mode 100644 index 769ea04da..000000000 --- a/test/utils/ProofUtils.sol +++ /dev/null @@ -1,36 +0,0 @@ -/// SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.24; - -import {ProofParsing} from "lib/eigenlayer-contracts/src/test/utils/ProofParsing.sol"; -import {BeaconChainProofs} from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; -import "forge-std/StdJson.sol"; - -contract ProofUtils is ProofParsing { - - constructor(string memory path) { - setJSON((path)); - } - - function _getStateRootProof() external returns (BeaconChainProofs.StateRootProof memory) { - return BeaconChainProofs.StateRootProof( - getBeaconStateRoot(), abi.encodePacked(ProofParsing.getStateRootProof()) - ); - } - - function convertBytes32ArrayToBytesArray(bytes32[] memory input) public pure returns (bytes[] memory) { - bytes[] memory output = new bytes[](input.length); - for (uint256 i = 0; i < input.length; i++) { - output[i] = abi.encodePacked(input[i]); - } - return output; - } - - function _getValidatorFieldsProof() public returns(bytes[] memory) { - bytes32[] memory validatorFieldsProof = new bytes32[](46); - for (uint i = 0; i < 46; i++) { - prefix = string.concat(".ValidatorProof[", string.concat(vm.toString(i), "]")); - validatorFieldsProof[i] = (stdJson.readBytes32(proofConfigJson, prefix)); - } - return convertBytes32ArrayToBytesArray(validatorFieldsProof); - } -} \ No newline at end of file diff --git a/test/utils/StakingNodeTestBase.sol b/test/utils/StakingNodeTestBase.sol new file mode 100644 index 000000000..cf6073962 --- /dev/null +++ b/test/utils/StakingNodeTestBase.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: BSD 3-Clause License +pragma solidity ^0.8.24; +import {StakingNodesManager} from "src/StakingNodesManager.sol"; +import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import {ynETH} from "src/ynETH.sol"; +import {RewardsReceiver} from "src/RewardsReceiver.sol"; +import {RewardsDistributor} from "src/RewardsDistributor.sol"; +import {ProxyAdmin} from "lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; +import {IStakingNodesManager} from "src/interfaces/IStakingNodesManager.sol"; +import {IStakingNode} from "src/interfaces/IStakingNodesManager.sol"; +// import {IBeaconChainOracle} from "lib/eigenlayer-contracts/src/contracts/interfaces/IBeaconChainOracle.sol"; +// import {IDelayedWithdrawalRouter} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelayedWithdrawalRouter.sol"; +import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IDelegationManager} from "lib/eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ITransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {ScenarioBaseTest} from "test/scenarios/ScenarioBaseTest.sol"; +import { Invariants } from "test/scenarios/Invariants.sol"; +import {stdStorage, StdStorage} from "forge-std/Test.sol"; +import {BytesLib} from "lib/eigenlayer-contracts/src/contracts/libraries/BytesLib.sol"; +// import { MockEigenLayerBeaconOracle } from "test/mocks/MockEigenLayerBeaconOracle.sol"; + +import {UpgradeableBeacon} from "lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import {TestStakingNodesManagerV2} from "test/mocks/TestStakingNodesManagerV2.sol"; +import {TestStakingNodeV2} from "test/mocks/TestStakingNodeV2.sol"; + +import {BeaconChainProofs} from "lib/eigenlayer-contracts/src/contracts/libraries/BeaconChainProofs.sol"; +import {Merkle} from "lib/eigenlayer-contracts/src/contracts/libraries/Merkle.sol"; +import { ProofParsingV1 } from "test/eigenlayer-utils/ProofParsingV1.sol"; +import {Utils} from "script/Utils.sol"; +import {beaconChainETHStrategy} from "src/Constants.sol"; +import {Vm} from "lib/forge-std/src/Vm.sol"; +import "forge-std/console.sol"; + +contract StakingNodeTestBase is ScenarioBaseTest, ProofParsingV1 { + + struct ValidatorProofs { + BeaconChainProofs.StateRootProof stateRootProof; + uint40[] validatorIndices; + bytes[] withdrawalCredentialProofs; + bytes[] validatorFieldsProofs; + bytes32[][] validatorFields; + } + + struct ValidatorWithdrawalProofParams { + BeaconChainProofs.StateRootProof stateRootProof; + bytes32[][] validatorFields; + bytes[] validatorFieldsProofs; + bytes32[][] withdrawalFields; + // BeaconChainProofs.WithdrawalProof[] withdrawalProofs; + } + + struct WithdrawAction { + uint256 nodeId; + uint256 amountToReinvest; + uint256 amountToQueue; + } + + function _getLatestBlockRoot() public returns (bytes32) { + return getLatestBlockRoot(); + } + + function getWithdrawalCredentialParams() public returns (ValidatorProofs memory) { + ValidatorProofs memory validatorProofs; + + validatorProofs.validatorIndices = new uint40[](1); + validatorProofs.withdrawalCredentialProofs = new bytes[](1); + validatorProofs.validatorFieldsProofs = new bytes[](1); + validatorProofs.validatorFields = new bytes32[][](1); + + + //bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof()); + // Set beacon state root, validatorIndex + validatorProofs.stateRootProof.beaconStateRoot = getBeaconStateRoot(); + validatorProofs.stateRootProof.proof = getStateRootProof(); + validatorProofs.validatorIndices[0] = uint40(getValidatorIndex()); + validatorProofs.withdrawalCredentialProofs[0] = abi.encodePacked(getWithdrawalCredentialProof()); // Validator fields are proven here + //validatorProofs.validatorFieldsProofs[0] = validatorFieldsProof; + validatorProofs.validatorFields[0] = getValidatorFields(); + + return validatorProofs; + } + + function getValidatorWithdrawalProofParams() public returns (ValidatorWithdrawalProofParams memory) { + ValidatorWithdrawalProofParams memory params; + + params.validatorFieldsProofs = new bytes[](1); + params.validatorFields = new bytes32[][](1); + params.withdrawalFields = new bytes32[][](1); + // params.withdrawalProofs = new BeaconChainProofs.WithdrawalProof[](1); + + params.stateRootProof.beaconStateRoot = getBeaconStateRoot(); + params.stateRootProof.proof = getStateRootProof(); + params.validatorFields[0] = getValidatorFields(); + params.withdrawalFields[0] = getWithdrawalFields(); + // params.withdrawalProofs[0] = _getWithdrawalProof(); + params.validatorFieldsProofs[0] = abi.encodePacked(getValidatorProof()); + + return params; + } + + function bytes32ToData(bytes32 data) public pure returns (address) { + return address(uint160(uint256(data))); + } + + function getWithdrawalAddress() public returns (address) { + bytes32[] memory validatorFields = getValidatorFields(); + return bytes32ToData(validatorFields[1]); + } + + + // function _getWithdrawalProof() internal returns (BeaconChainProofs.WithdrawalProof memory) { + // { + // bytes32 blockRoot = getBlockRoot(); + // bytes32 slotRoot = getSlotRoot(); + // bytes32 timestampRoot = getTimestampRoot(); + // bytes32 executionPayloadRoot = getExecutionPayloadRoot(); + + // return + // BeaconChainProofs.WithdrawalProof( + // abi.encodePacked(getWithdrawalProofDeneb()), + // abi.encodePacked(getSlotProof()), + // abi.encodePacked(getExecutionPayloadProof()), + // abi.encodePacked(getTimestampProofDeneb()), + // abi.encodePacked(getHistoricalSummaryProof()), + // uint64(getBlockRootIndex()), + // uint64(getHistoricalSummaryIndex()), + // uint64(getWithdrawalIndex()), + // blockRoot, + // slotRoot, + // timestampRoot, + // executionPayloadRoot + // ); + // } + // } + + // function setupForVerifyWithdrawalCredentials(uint256 nodeId, string memory path) public { + + // setJSON(path); + + // IStakingNode stakingNodeInstance = stakingNodesManager.nodes(nodeId); + + // MockEigenLayerBeaconOracle mockBeaconOracle = new MockEigenLayerBeaconOracle(); + + // address eigenPodManagerOwner = OwnableUpgradeable(address(eigenPodManager)).owner(); + // vm.prank(eigenPodManagerOwner); + // eigenPodManager.updateBeaconChainOracle(IBeaconChainOracle(address(mockBeaconOracle))); + + // // set existing EigenPod to be the EigenPod of the StakingNode for the + // // purpose of testing verifyWithdrawalCredentials + // address eigenPodAddress = getWithdrawalAddress(); + + // assertEq(eigenPodAddress, address(stakingNodeInstance.eigenPod()), "EigenPod address does not match the expected address"); + + // bytes32 latestBlockRoot = _getLatestBlockRoot(); + // mockBeaconOracle.setOracleBlockRootAtTimestamp(latestBlockRoot); + // } + + function runSystemStateInvariants( + uint256 previousTotalAssets, + uint256 previousTotalSupply, + uint256[] memory previousStakingNodeBalances + ) public { + assertEq(yneth.totalAssets(), previousTotalAssets, "Total assets integrity check failed"); + assertEq(yneth.totalSupply(), previousTotalSupply, "Share mint integrity check failed"); + for (uint i = 0; i < previousStakingNodeBalances.length; i++) { + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(i); + uint256 currentStakingNodeBalance = stakingNodeInstance.getETHBalance(); + assertEq(currentStakingNodeBalance, previousStakingNodeBalances[i], "Staking node balance integrity check failed for node ID: "); + } + } + + function runSystemStateInvariants( + uint256 previousTotalAssets, + uint256 previousTotalSupply, + uint256[] memory previousStakingNodeBalances, + IStakingNodesManager.WithdrawalAction[] memory withdrawActions, + uint256 previousYnETHRedemptionAssetsVaultBalance, + uint256 previousYnETHBalance + ) public { + assertEq(yneth.totalAssets(), previousTotalAssets, "Total assets integrity check failed"); + assertEq(yneth.totalSupply(), previousTotalSupply, "Share mint integrity check failed"); + for (uint i = 0; i < previousStakingNodeBalances.length; i++) { + IStakingNode stakingNodeInstance = stakingNodesManager.nodes(i); + uint256 currentStakingNodeBalance = stakingNodeInstance.getETHBalance(); + uint256 expectedBalance = previousStakingNodeBalances[i]; + for (uint j = 0; j < withdrawActions.length; j++) { + if (withdrawActions[j].nodeId == i) { + expectedBalance -= withdrawActions[j].amountToQueue + withdrawActions[j].amountToReinvest; + } + } + assertEq(currentStakingNodeBalance, expectedBalance, "Staking node balance integrity check failed for node ID: "); + } + + uint256 currentYnETHRedemptionAssetsVaultBalance = ynETHRedemptionAssetsVaultInstance.availableRedemptionAssets(); + uint256 expectedVaultBalance = previousYnETHRedemptionAssetsVaultBalance; + uint256 expectedYnETHBalance = previousYnETHBalance; + for (uint j = 0; j < withdrawActions.length; j++) { + expectedVaultBalance += withdrawActions[j].amountToQueue; + expectedYnETHBalance += withdrawActions[j].amountToReinvest; + } + assertEq(currentYnETHRedemptionAssetsVaultBalance, expectedVaultBalance, "YnETH Redemption Assets Vault balance integrity check failed after withdrawals"); + assertEq(address(yneth).balance, expectedYnETHBalance, "YnETH balance integrity check failed after withdrawals"); + assertEq(yneth.totalDepositedInPool(), expectedYnETHBalance, "Total Deposited in Pool integrity check failed after withdrawals"); + } + + function completeQueuedWithdrawals(IStakingNode stakingNodeInstance, uint256 withdrawalAmount) public { + uint256 nonce = delegationManager.cumulativeWithdrawalsQueued(address(stakingNodeInstance)) - 1; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = beaconChainETHStrategy; + + uint256[] memory shares = new uint256[](1); + shares[0] = withdrawalAmount; + IDelegationManager.Withdrawal memory withdrawal = IDelegationManager.Withdrawal({ + staker: address(stakingNodeInstance), + delegatedTo: delegationManager.delegatedTo(address(stakingNodeInstance)), + withdrawer: address(stakingNodeInstance), + nonce: nonce, + startBlock: uint32(block.number), + strategies: strategies, + shares: shares + }); + + IDelegationManager.Withdrawal[] memory withdrawals = new IDelegationManager.Withdrawal[](1); + withdrawals[0] = withdrawal; + + uint256[] memory middlewareTimesIndexes = new uint256[](1); + middlewareTimesIndexes[0] = 0; // value is not used, as per EigenLayer docs + + // Advance time so the withdrawal can be completed + vm.roll(block.number + delegationManager.minWithdrawalDelayBlocks() + 1); + + vm.expectRevert(bytes4(keccak256("NotStakingNodesWithdrawer()"))); + stakingNodeInstance.completeQueuedWithdrawals(withdrawals, middlewareTimesIndexes); + + vm.prank(actors.ops.STAKING_NODES_WITHDRAWER); + stakingNodeInstance.completeQueuedWithdrawals(withdrawals, middlewareTimesIndexes); + } + + function getAllStakingNodeBalances() public view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](stakingNodesManager.nodesLength()); + for (uint256 i = 0; i < stakingNodesManager.nodesLength(); i++) { + IStakingNode stakingNode = stakingNodesManager.nodes(i); + balances[i] = stakingNode.getETHBalance(); + } + return balances; + } + + // // TODO: Update This + // function sumTotalDelayedWithdrawalsForUser(address user) public view returns (uint256 totalDelayedWithdrawals) { + + // IDelayedWithdrawalRouter.DelayedWithdrawal[] memory delayedWithdrawals + // = delayedWithdrawalRouter.getUserDelayedWithdrawals(user); + // for (uint256 j = 0; j < delayedWithdrawals.length; j++) { + // totalDelayedWithdrawals += delayedWithdrawals[j].amount; + // } + // } + + function finalizeRequest(uint256 tokenId) public returns (uint256) { + vm.prank(actors.ops.REQUEST_FINALIZER); + return ynETHWithdrawalQueueManager.finalizeRequestsUpToIndex(tokenId + 1); + } +} \ No newline at end of file