From 70c4431b77e0c83792cdb093159414eb3fb0520f Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:20:20 +0000 Subject: [PATCH] fix: update proof generation script + generate.js --- .env.example | 3 +- package-lock.json | 291 +++++++++++++++++++- package.json | 2 + script/generate.js | 186 +++++++++++-- script/integration/1_DeployBootstrap.s.sol | 4 +- script/integration/2_VerifyDepositNST.s.sol | 10 +- script/integration/deposit.sh | 35 ++- script/integration/prove.sh | 35 ++- src/core/Bootstrap.sol | 22 ++ src/libraries/NetworkConstants.sol | 1 + src/storage/BootstrapStorage.sol | 4 + src/storage/ExoCapsuleStorage.sol | 20 +- test/foundry/unit/NetworkConfig.t.sol | 2 +- 13 files changed, 547 insertions(+), 68 deletions(-) diff --git a/.env.example b/.env.example index ec3dbc6a..56ed6081 100644 --- a/.env.example +++ b/.env.example @@ -22,13 +22,14 @@ BOOTSTRAP_ADDRESS= EXCHANGE_RATES= BASE_GENESIS_FILE_PATH= RESULT_GENESIS_FILE_PATH= +BEACON_CHAIN_ENDPOINT= # For contract verification ETHERSCAN_API_KEY= # These are used for integration testing ETH PoS INTEGRATION_DEPOSIT_ADDRESS=0x6969696969696969696969696969696969696969 -INTEGRATION_SECONDS_PER_EPOCH=4 +INTEGRATION_SECONDS_PER_SLOT=4 INTEGRATION_SLOTS_PER_EPOCH=3 INTEGRATION_BEACON_GENESIS_TIMESTAMP= INTEGRATION_DENEB_TIMESTAMP= diff --git a/package-lock.json b/package-lock.json index e66d06cc..1c7ee89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@lodestar/api": "^1.23.0", "abbrev": "^1.0.9", "abstract-level": "^1.0.3", "acorn": "^8.11.2", @@ -431,6 +432,64 @@ "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==" }, + "node_modules/@chainsafe/hashtree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree/-/hashtree-1.0.1.tgz", + "integrity": "sha512-bleu9FjqBeR/l6W1u2Lz+HsS0b0LLJX2eUt3hOPBN7VqOhidx8wzkVh2S7YurS+iTQtfdK4K5QU9tcTGNrGwDg==", + "engines": { + "node": ">= 18" + }, + "optionalDependencies": { + "@chainsafe/hashtree-darwin-arm64": "1.0.1", + "@chainsafe/hashtree-linux-arm64-gnu": "1.0.1", + "@chainsafe/hashtree-linux-x64-gnu": "1.0.1" + } + }, + "node_modules/@chainsafe/hashtree-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-darwin-arm64/-/hashtree-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-+KmEgQMpO7FDL3klAcpXbQ4DPZvfCe0qSaBBrtT4vLF8V1JGm3sp+j7oibtxtOsLKz7nJMiK1pZExi7vjXu8og==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@chainsafe/hashtree-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-linux-arm64-gnu/-/hashtree-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-p1hnhGq2aFY+Zhdn1Q6L/6yLYNKjqXfn/Pc8jiM0e3+Lf/hB+yCdqYVu1pto26BrZjugCFZfupHaL4DjUTDttw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@chainsafe/hashtree-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-linux-x64-gnu/-/hashtree-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-uCIGuUWuWV0LiB4KLMy6JFa7Jp6NmPl3hKF5BYWu8TzUBe7vSXMZfqTzGxXPggFYN2/0KymfRdG9iDCOJfGRqg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@chainsafe/persistent-merkle-tree": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", @@ -1243,6 +1302,194 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@lodestar/api": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lodestar/api/-/api-1.23.0.tgz", + "integrity": "sha512-ackvkEvA2EPyvtZaniMZvM/IBrpwy+BcA7EQGTxMgzILngkkoL9WZukGb0Mq2rVe6ccU+Q0YXfG/dlByW/tW4Q==", + "dependencies": { + "@chainsafe/persistent-merkle-tree": "^0.8.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", + "eventsource": "^2.0.2", + "qs": "^6.11.1" + } + }, + "node_modules/@lodestar/api/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==" + }, + "node_modules/@lodestar/api/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, + "node_modules/@lodestar/api/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, + "node_modules/@lodestar/config": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lodestar/config/-/config-1.23.0.tgz", + "integrity": "sha512-K4MlD/LjW1IvQQL1I3QTYx1HOUITgKRmyazv9Xm7NlIk073esQW7iK0wVO8nJfW5gglTK0amQnC9SFgcGOqqYg==", + "dependencies": { + "@chainsafe/ssz": "^0.18.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0" + } + }, + "node_modules/@lodestar/config/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==" + }, + "node_modules/@lodestar/config/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, + "node_modules/@lodestar/config/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, + "node_modules/@lodestar/params": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lodestar/params/-/params-1.23.0.tgz", + "integrity": "sha512-NphFvYezC6RQg8xKUFQmEMm2YfntuirNSKo+EId1/LntXtzcZM1QTRNyuW9GJqA7mnMi+ZKs7NvE0kqU9Yocdg==" + }, + "node_modules/@lodestar/types": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lodestar/types/-/types-1.23.0.tgz", + "integrity": "sha512-7bzS4ZaW5n+rKdErycxnP+oxzM+JaEolTaIjoUMWbuS6jADZsgh74kbJVgS2yNO6HV6a9o0igp11jUg1UcnSLw==", + "dependencies": { + "@chainsafe/ssz": "^0.18.0", + "@lodestar/params": "^1.23.0", + "ethereum-cryptography": "^2.0.0" + } + }, + "node_modules/@lodestar/types/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==" + }, + "node_modules/@lodestar/types/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, + "node_modules/@lodestar/types/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, + "node_modules/@lodestar/types/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@lodestar/types/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@lodestar/types/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@lodestar/types/node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@lodestar/types/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/@lodestar/utils": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lodestar/utils/-/utils-1.23.0.tgz", + "integrity": "sha512-J0+0Mo2ufWrdg8nj9ciujOGrIJK+6sczYpSpNgyxupq+2i/XGNpjxXLqXznpW5KPOeWEPkRgR99lp/UYbTnFWA==", + "dependencies": { + "@chainsafe/as-sha256": "^0.5.0", + "any-signal": "3.0.1", + "bigint-buffer": "^1.1.5", + "case": "^1.6.3", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@lodestar/utils/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==" + }, "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", @@ -1817,9 +2064,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -2386,6 +2633,11 @@ "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" }, + "node_modules/any-signal": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-3.0.1.tgz", + "integrity": "sha512-xgZgJtKEa9YmDqXodIgl7Fl1C8yNXr8w6gXjqK3LW4GcEiYT+6AQfJSE/8SPsEpLLmcvbv8YU+qet94UewHxqg==" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2538,6 +2790,18 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bigint-crypto-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", @@ -2562,6 +2826,14 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/blakejs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", @@ -3787,6 +4059,14 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -3841,6 +4121,11 @@ "reusify": "^1.0.4" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/package.json b/package.json index 87cd8749..0f8e600c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test": "test" }, "dependencies": { + "@lodestar/api": "^1.23.0", "abbrev": "^1.0.9", "abstract-level": "^1.0.3", "acorn": "^8.11.2", @@ -386,6 +387,7 @@ "scripts": { "test": "mocha" }, + "type": "module", "keywords": [], "author": "", "license": "ISC" diff --git a/script/generate.js b/script/generate.js index 9b680cc5..8ef2597b 100644 --- a/script/generate.js +++ b/script/generate.js @@ -11,14 +11,16 @@ const clientChainInfo = { }; // this must be in the same order as whitelistTokens const tokenMetaInfos = [ - 'Exocore testnet ETH', // first we did push exoETH - 'Lido wrapped staked ETH', // then push wstETH + 'Staked ETH', + 'Exocore testnet ETH', + 'Lido wrapped staked ETH', ]; // this must be in the same order as whitelistTokens // they are provided because the symbol may not match what we are using from the price feeder. // for example, exoETH is not a real token and we are using the price feed for ETH. +// the script will take care of mapping the duplicates to a common token for x/oracle params. const tokenNamesForOracle = [ - 'ETH', 'wstETH' // not case sensitive + 'ETH', 'ETH', 'wstETH' // not case sensitive ] const nativeChain = { "name": "Exocore", @@ -39,14 +41,19 @@ const nativeAsset = { }, "staking_total_amount": "0" }; +const EXOCORE_BECH32_PREFIX = 'exo'; +const VIRTUAL_STAKED_ETH_ADDR = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" +const GWEI_TO_WEI = new Decimal(1e9); -const exocoreBech32Prefix = 'exo'; +import dotenv from 'dotenv'; +dotenv.config(); +import { decode } from 'bech32'; +import { promises as fs } from 'fs'; +import { Web3 } from 'web3'; +import Decimal from 'decimal.js'; -require('dotenv').config(); -let { decode } = require('bech32'); -const fs = require('fs').promises; -const { Web3 } = require('web3'); -const Decimal = require('decimal.js'); +import { getClient } from "@lodestar/api"; +import { config } from "@lodestar/config/default"; const isValidBech32 = (address) => { try { @@ -54,7 +61,7 @@ const isValidBech32 = (address) => { if (!prefix || !words.length) { return false; } - return prefix === exocoreBech32Prefix; + return prefix === EXOCORE_BECH32_PREFIX; } catch (error) { // If there's any error in decoding, return false return false; @@ -63,10 +70,14 @@ const isValidBech32 = (address) => { // Load variables from .env file -const { CLIENT_CHAIN_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, RESULT_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; +const { BEACON_CHAIN_ENDPOINT, CLIENT_CHAIN_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, RESULT_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; + +import pkg from 'js-sha3'; +const { keccak256 } = pkg; + +import JSONbig from 'json-bigint'; +const jsonBig = JSONbig({ useNativeBigInt: true }); -const { keccak256 } = require('js-sha3'); -const JSONbig = require('json-bigint')({ "useNativeBigInt": true }); function getChainIDWithoutPrevision(chainID) { const splitStr = chainID.split('-'); @@ -96,13 +107,27 @@ async function updateGenesisFile() { // Create contract instance const myContract = new web3.eth.Contract(contractABI, BOOTSTRAP_ADDRESS); + // Create beacon API client + const api = getClient({baseUrl: BEACON_CHAIN_ENDPOINT}, {config}); + const spec = (await api.config.getSpec()).value(); + const maxEffectiveBalance = new Decimal(spec.MAX_EFFECTIVE_BALANCE).mul(GWEI_TO_WEI); + const ejectIonBalance = new Decimal(spec.EJECTION_BALANCE).mul(GWEI_TO_WEI); + const slotsPerEpoch = spec.SLOTS_PER_EPOCH; + let lastHeader = (await api.beacon.getBlockHeader({blockId: "finalized"})).value(); + const finalizedSlot = lastHeader.header.message.slot; + const finalizedEpoch = Math.floor(finalizedSlot / slotsPerEpoch); + if (finalizedSlot % slotsPerEpoch != 0) { + // change the header + lastHeader = (await api.beacon.getBlockHeader({blockId: finalizedEpoch * slotsPerEpoch})).value(); + } + const stateRoot = web3.utils.bytesToHex(lastHeader.header.message.stateRoot); // Read exchange rates const exchangeRates = EXCHANGE_RATES.split(',').map(Decimal); // Read the genesis file const genesisData = await fs.readFile(BASE_GENESIS_FILE_PATH); - const genesisJSON = JSONbig.parse(genesisData); + const genesisJSON = jsonBig.parse(genesisData); const height = parseInt(genesisJSON.initial_height, 10); const bootstrapped = await myContract.methods.bootstrapped().call(); @@ -182,6 +207,9 @@ async function updateGenesisFile() { const decimals = []; const supportedTokens = []; const assetIds = []; + const oracleTokens = {}; + const oracleTokenFeeders = []; + let offset = 0; for (let i = 0; i < supportedTokensCount; i++) { let token = await myContract.methods.getWhitelistedTokenAtIndex(i).call(); const deposit_amount = await myContract.methods.depositsByToken(token.tokenAddress).call(); @@ -203,24 +231,34 @@ async function updateGenesisFile() { assetIds.push(token.tokenAddress.toLowerCase() + clientChainSuffix); const oracleToken = { name: tokenNamesForOracle[i], - chain_id: 1, // constant intentionally, representing the first chain in the list + chain_id: 1, // constant intentionally, representing the first chain in the list (after the reserved blank one) contract_address: token.tokenAddress, active: true, asset_id: token.tokenAddress.toLowerCase() + clientChainSuffix, decimal: 8, // price decimals, not token decimals } - genesisJSON.app_state.oracle.params.tokens.push(oracleToken); const oracleTokenFeeder = { - token_id: (i + 1).toString(), // first is reserved + index: offset, + token_id: (i + 1 - offset).toString(), // first is reserved rule_id: "1", start_round_id: "1", start_base_block: (height + 10000).toString(), interval: "30", end_block: "0", } - genesisJSON.app_state.oracle.params.token_feeders.push(oracleTokenFeeder); + if (oracleTokens.name in oracleTokens) { + oracleTokens[oracleTokens.name].asset_id += ',' + oracleToken.asset_id; + offset += 1; + } else { + oracleTokens[oracleTokens.name] = oracleToken; + oracleTokenFeeders.push(oracleTokenFeeder); + } // break; } + genesisJSON.app_state.oracle.params.tokens = Object.values(oracleTokens) + .sort((a, b) => {a.index - b.uindex}) + .map(({index, ...rest}) => rest); + genesisJSON.app_state.oracle.params.token_feeders = oracleTokenFeeders; supportedTokens.sort((a, b) => { if (a.asset_basic_info.symbol < b.asset_basic_info.symbol) { return -1; @@ -240,6 +278,9 @@ async function updateGenesisFile() { } const depositorsCount = await myContract.methods.getDepositorsCount().call(); const deposits = []; + const nativeTokenDepositors = []; + const staker_infos = []; + let staker_index_counter = 0; for (let i = 0; i < depositorsCount; i++) { const stakerAddress = await myContract.methods.depositors(i).call(); const depositsByStaker = []; @@ -247,15 +288,98 @@ async function updateGenesisFile() { // do not reuse the older array since it has been sorted. const tokenAddress = (await myContract.methods.getWhitelistedTokenAtIndex(j).call()).tokenAddress; - const depositValue = await myContract.methods.totalDepositAmounts( + let depositValue = new Decimal((await myContract.methods.totalDepositAmounts( stakerAddress, tokenAddress - ).call(); - const withdrawableValue = await myContract.methods.withdrawableAmounts( + ).call()).toString()); + let withdrawableValue = new Decimal((await myContract.methods.withdrawableAmounts( stakerAddress, tokenAddress - ).call(); + ).call()).toString()); + if ((tokenAddress == VIRTUAL_STAKED_ETH_ADDR) && (depositValue > 0)) { + // we have to use the effective balance calculation + nativeTokenDepositors.push(stakerAddress.toLowerCase()); + const pubKeyCount = await myContract.methods.getPubkeysCount(stakerAddress).call(); + const pubKeys = []; + for(let k = 0; k < pubKeyCount; k++) { + // TODO: the contract stores not pubkeys but pubkey hashes. figure out where + // to get the correct value. it affects ClientChainGateway and the network + // overall as well. + pubKeys.push("0x98db81971df910a5d46314d21320f897060d76fdf137d22f0eb91a8693a4767d2a22730a3aaa955f07d13ad604f968e9"); + } + const validatorStates = (await api.beacon.getStateValidators({stateId: stateRoot, validatorIds: pubKeys})).value(); + let totalEffectiveBalance = new Decimal(0);; + for(let k = 0; k < validatorStates.length; k++) { + const validator = validatorStates[k]; + // https://hackmd.io/@protolambda/validator_status + // it is sufficient to check for active_ongoing + if (validator.status != "active_ongoing") { + console.log(`Skipping staker ${stakerAddress} due to inactive validator ${pubKeys[k]}`); + continue; + } + const valEffectiveBalance = new Decimal(validator.validator.effectiveBalance).mul(GWEI_TO_WEI); + if (valEffectiveBalance.gt(maxEffectiveBalance)) { + throw new Error(`The effective balance of staker ${stakerAddress} exceeds the maximum effective balance.`); + } + if (valEffectiveBalance.lt(ejectIonBalance)) { + console.log(`Skipping staker ${stakerAddress} due to low validator balance ${valEffectiveBalance}`); + continue; + } + totalEffectiveBalance = totalEffectiveBalance.plus(valEffectiveBalance); + } + console.log(`Total effective balance for staker ${stakerAddress}: ${totalEffectiveBalance}`); + console.log(`Total deposit value for staker ${stakerAddress}: ${depositValue}`); + if (depositValue > totalEffectiveBalance) { + console.log("Staker has more deposit than effective balance."); + // deposited 32 ETH and left with 31 ETH, aka downtime slashing + let toSlash = depositValue.minus(totalEffectiveBalance); + // if withdrawableValue can take the full slashing, do it. + if (withdrawableValue.gt(toSlash)) { + withdrawableValue = withdrawableValue.minus(toSlash); + } else { + // if not, only do it partially. + toSlash = toSlash.minus(withdrawableValue); + withdrawableValue = new Decimal(0); + } + // there is still some left, so do it from the deposit. + if (toSlash.gt(0)) { + if (depositValue.gt(toSlash)) { + depositValue = depositValue.minus(toSlash); + } else { + console.log(`Skipping staker ${stakerAddress} due to insufficient deposit ${depositValue}`); + continue; + } + } + let pendingSlashAmount = toSlash.sub(withdrawableValue); + if (pendingSlashAmount.gt(0)) { + withdrawableValue = withdrawableValue.minus(pendingSlashAmount); + depositValue = depositValue.minus(pendingSlashAmount); + } + } else if (depositValue < totalEffectiveBalance) { + // deposited 32 ETH and left with 33 ETH, aka rewards + const delta = totalEffectiveBalance.minus(depositValue); + depositValue = depositValue.plus(delta); + withdrawableValue = withdrawableValue.plus(delta); + } + staker_infos.push({ + staker_addr: stakerAddress.toLowerCase(), + staker_index: staker_index_counter, + validator_pubkey_list: pubKeys, + balance_list: [ + { + // TODO: check these values with Qing + round_id: 0, + block: height, + index: 0, + change: 0, + balance: depositValue.toString(), + } + ] + }); + staker_index_counter += 1; + } const depositByStakerForAsset = { asset_id: tokenAddress.toLowerCase() + clientChainSuffix, info: { + // adjusted for slashing by ETH beacon chain total_deposit_amount: depositValue.toString(), withdrawable_amount: withdrawableValue.toString(), pending_undelegation_amount: "0", @@ -677,6 +801,20 @@ async function updateGenesisFile() { genesisJSON.app_state.delegation.delegation_states = delegation_states; genesisJSON.app_state.delegation.stakers_by_operator = stakers_by_operator; + // x/oracle - native restaking for ETH + genesisJSON.app_state.oracle.staker_list_assets = [ + { + asset_id: VIRTUAL_STAKED_ETH_ADDR.toLowerCase() + clientChainSuffix, + staker_list: { + staker_addrs: nativeTokenDepositors, + } + } + ]; + genesisJSON.app_state.oracle.staker_infos_assets = { + asset_id: VIRTUAL_STAKED_ETH_ADDR.toLowerCase() + clientChainSuffix, + staker_infos: staker_infos, + }; + // add the native chain and at the end so that count-related issues don't arise. genesisJSON.app_state.assets.client_chains.push(nativeChain); genesisJSON.app_state.assets.tokens.push(nativeAsset); @@ -686,12 +824,12 @@ async function updateGenesisFile() { nativeAsset.asset_basic_info.layer_zero_chain_id.toString(16) ); - await fs.writeFile(RESULT_GENESIS_FILE_PATH, JSONbig.stringify(genesisJSON, null, 2)); + await fs.writeFile(RESULT_GENESIS_FILE_PATH, jsonBig.stringify(genesisJSON, null, 2)); console.log('Genesis file updated successfully.'); } catch (error) { console.error('Error updating genesis file:', error.message); console.error('Stack trace:', error.stack); } -} +}; updateGenesisFile(); \ No newline at end of file diff --git a/script/integration/1_DeployBootstrap.s.sol b/script/integration/1_DeployBootstrap.s.sol index a38f4939..4f1aa1ce 100644 --- a/script/integration/1_DeployBootstrap.s.sol +++ b/script/integration/1_DeployBootstrap.s.sol @@ -123,11 +123,11 @@ contract DeployContracts is Script { beaconGenesisTimestamp = vm.envUint("INTEGRATION_BEACON_GENESIS_TIMESTAMP"); require(beaconGenesisTimestamp > 0, "Beacon timestamp must be set"); // can not read uint64 from env - uint256 secondsPerSlot_ = vm.envOr("INTEGRATION_SECONDS_PER_SLOT", uint256(4)); + uint256 secondsPerSlot_ = vm.envUint("INTEGRATION_SECONDS_PER_SLOT"); require(secondsPerSlot_ > 0, "Seconds per slot must be set"); require(secondsPerSlot_ <= type(uint64).max, "Seconds per slot must be less than or equal to uint64 max"); secondsPerSlot = uint64(secondsPerSlot_); - uint256 slotsPerEpoch_ = vm.envOr("INTEGRATION_SLOTS_PER_EPOCH", uint256(3)); + uint256 slotsPerEpoch_ = vm.envUint("INTEGRATION_SLOTS_PER_EPOCH"); require(slotsPerEpoch_ > 0, "Slots per epoch must be set"); require(slotsPerEpoch_ <= type(uint64).max, "Slots per epoch must be less than or equal to uint64 max"); slotsPerEpoch = uint64(slotsPerEpoch_); diff --git a/script/integration/2_VerifyDepositNST.s.sol b/script/integration/2_VerifyDepositNST.s.sol index 9a8a5453..01191c6c 100644 --- a/script/integration/2_VerifyDepositNST.s.sol +++ b/script/integration/2_VerifyDepositNST.s.sol @@ -19,7 +19,6 @@ contract VerifyDepositNST is Script { using Endian for bytes32; using stdJson for string; - bytes32[] validatorContainer; BeaconChainProofs.ValidatorContainerProof validatorProof; bytes32 beaconBlockRoot; @@ -40,7 +39,9 @@ contract VerifyDepositNST is Script { } function run() external { + bytes32[] memory validatorContainer; vm.startBroadcast(nstDepositor); + Bootstrap bootstrap = Bootstrap(bootstrapAddress); string memory data = vm.readFile("script/integration/proof.json"); // load the validator container validatorContainer = data.readBytes32Array(".validatorContainer"); @@ -55,9 +56,12 @@ contract VerifyDepositNST is Script { // since the oracle is not necessarily active during integration testing, trigger it manually BeaconOracle oracle = BeaconOracle(beaconOracleAddress); oracle.addTimestamp(validatorProof.beaconBlockTimestamp); - // now, the transaction - Bootstrap bootstrap = Bootstrap(bootstrapAddress); + // now, the transactions bootstrap.verifyAndDepositNativeStake(validatorContainer, validatorProof); + // delegate only a small portion of the deposit for our test + bootstrap.delegateTo( + "exo1rtg0cgw94ep744epyvanc0wdd5kedwql73vlmr", address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), 18 ether + ); vm.stopBroadcast(); } diff --git a/script/integration/deposit.sh b/script/integration/deposit.sh index 9b9d60d8..bc8f2458 100755 --- a/script/integration/deposit.sh +++ b/script/integration/deposit.sh @@ -4,24 +4,35 @@ SCRIPT_DIR=$(dirname "$(readlink -f "$0")") # Fetch the validator details and save them to container.json -curl -s -X GET "http://localhost:3500/eth/v1/beacon/genesis" -H "accept: application/json" | jq > "$SCRIPT_DIR/genesis.json" +curl -s -X GET "http://localhost:3500/eth/v1/beacon/genesis" -H "accept: application/json" | jq >"$SCRIPT_DIR/genesis.json" + +# Fetch the spec sheet and save it to spec.json +curl -s http://localhost:3500/eth/v1/config/spec | jq >"$SCRIPT_DIR/spec.json" # Ensure the request was successful if [ $? -ne 0 ]; then - echo "Error: Failed to fetch genesis data of the beacon chain." - exit 1 + echo "Error: Failed to fetch genesis data of the beacon chain." + exit 1 fi timestamp=$(jq -r .data.genesis_time "$SCRIPT_DIR/genesis.json") -# Since the devnet uses `--fork=deneb` and `DENEB_FORK_EPOCH: 0`, the deneb time is equal to the beacon genesis time - private_key=${NST_DEPOSITOR:-"0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd"} sender=$(cast wallet a $private_key) +deposit_address=$(jq -r .data.DEPOSIT_CONTRACT_ADDRESS "$SCRIPT_DIR/genesis.json") +slots_per_epoch=$(jq -r .data.SLOTS_PER_EPOCH "$SCRIPT_DIR/spec.json") +seconds_per_slot=$(jq -r .data.SECONDS_PER_SLOT "$SCRIPT_DIR/spec.json") + +# Since the devnet uses `--fork=deneb` and `DENEB_FORK_EPOCH: 0`, the deneb time is equal to the beacon genesis time +export INTEGRATION_BEACON_GENESIS_TIMESTAMP=$timestamp +export INTEGRATION_DENEB_TIMESTAMP=$timestamp +export INTEGRATION_SECONDS_PER_SLOT=$seconds_per_slot +export INTEGRATION_SLOTS_PER_EPOCH=$slots_per_epoch +export INTEGRATION_DEPOSIT_ADDRESS=$deposit_address +export SENDER=$sender -INTEGRATION_BEACON_GENESIS_TIMESTAMP=$timestamp \ -INTEGRATION_DENEB_TIMESTAMP=$timestamp \ -SENDER=$sender \ - forge script --skip-simulation script/integration/1_DeployBootstrap.s.sol \ - --rpc-url $CLIENT_CHAIN_RPC \ - --broadcast -vvvv --slow \ - --sender $SENDER \ No newline at end of file +forge script \ + --skip-simulation script/integration/1_DeployBootstrap.s.sol \ + --rpc-url $CLIENT_CHAIN_RPC \ + --broadcast -vvvv \ + --sender $SENDER \ + --evm-version cancun diff --git a/script/integration/prove.sh b/script/integration/prove.sh index 294a9122..5f02b510 100755 --- a/script/integration/prove.sh +++ b/script/integration/prove.sh @@ -4,21 +4,21 @@ SCRIPT_DIR=$(dirname "$(readlink -f "$0")") # Fetch the validator details and save them to container.json -curl -s -X GET "http://localhost:3500/eth/v1/beacon/states/head/validators/0x98db81971df910a5d46314d21320f897060d76fdf137d22f0eb91a8693a4767d2a22730a3aaa955f07d13ad604f968e9" -H "accept: application/json" | jq > "$SCRIPT_DIR/container.json" +curl -s -X GET "http://localhost:3500/eth/v1/beacon/states/head/validators/0x98db81971df910a5d46314d21320f897060d76fdf137d22f0eb91a8693a4767d2a22730a3aaa955f07d13ad604f968e9" -H "accept: application/json" | jq >"$SCRIPT_DIR/container.json" # Ensure the request was successful if [ $? -ne 0 ]; then - echo "Error: Failed to fetch validator details." - exit 1 + echo "Error: Failed to fetch validator details." + exit 1 fi # Fetch slots per epoch from the spec -slots_per_epoch=$(curl -s http://localhost:3500/eth/v1/config/spec | jq -r .data.SLOTS_PER_EPOCH) +slots_per_epoch=$(jq -r .data.SLOTS_PER_EPOCH "$SCRIPT_DIR/spec.json") # Ensure slots_per_epoch was fetched successfully if [ -z "$slots_per_epoch" ]; then - echo "Error: Failed to fetch SLOTS_PER_EPOCH." - exit 1 + echo "Error: Failed to fetch SLOTS_PER_EPOCH." + exit 1 fi # Extract the validator index and activation epoch from container.json @@ -27,18 +27,29 @@ epoch=$(jq -r .data.validator.activation_eligibility_epoch "$SCRIPT_DIR/containe # Ensure epoch value is valid if [ -z "$epoch" ] || [ "$epoch" == "null" ]; then - echo "Error: Activation epoch not found for the validator." - exit 1 + echo "Error: Activation epoch not found for the validator." + exit 1 fi # Calculate the slot number slot=$((slots_per_epoch * epoch)) +# # Wait till the slot is reached +# seconds_per_slot=$(jq -r .data.SECONDS_PER_SLOT "$SCRIPT_DIR/spec.json") +# while true; do +# current_slot=$(curl http://localhost:3500/eth/v1/beacon/headers | jq -r '.data[0].header.message.slot') +# if (( current_slot > slot )); then +# break +# fi +# echo "Waiting for slot $slot, current slot is $current_slot" +# sleep $seconds_per_slot +# done + # Now derive the proof using the proof generation binary, which must already be running configured to the localnet curl -X POST -H "Content-Type: application/json" \ - -d "{\"slot\": $slot, \"validator_index\": $validator_index}" \ - http://localhost:8989/v1/validator-proof | jq > "$SCRIPT_DIR/proof.json" + -d "{\"slot\": $slot, \"validator_index\": $validator_index}" \ + http://localhost:8989/v1/validator-proof | jq >"$SCRIPT_DIR/proof.json" forge script script/integration/2_VerifyDepositNST.s.sol --skip-simulation \ - --rpc-url $CLIENT_CHAIN_RPC --broadcast \ - --evm-version cancun # required, otherwise you get EvmError: NotActtivated + --rpc-url $CLIENT_CHAIN_RPC --broadcast \ + --evm-version cancun # required, otherwise you get EvmError: NotActivated diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index 383857b3..c2831fac 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -26,6 +26,7 @@ import {IVault} from "../interfaces/IVault.sol"; import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; import {Errors} from "../libraries/Errors.sol"; +import {ValidatorContainer} from "../libraries/ValidatorContainer.sol"; import {BootstrapStorage} from "../storage/BootstrapStorage.sol"; import {Action} from "../storage/GatewayStorage.sol"; @@ -48,6 +49,8 @@ contract Bootstrap is BootstrapLzReceiver { + using ValidatorContainer for bytes32[]; + /// @notice Constructor for the Bootstrap contract. /// @param endpoint_ is the address of the layerzero endpoint on Exocore chain /// @param config is the struct containing the values for immutable state variables @@ -687,6 +690,15 @@ contract Bootstrap is revert Errors.IndexOutOfBounds(); } address tokenAddress = whitelistTokens[index]; + if (tokenAddress == VIRTUAL_NST_ADDRESS) { + return TokenInfo({ + name: "Native Staked ETH", + symbol: "ETH", + tokenAddress: tokenAddress, + decimals: 18, + depositAmount: depositsByToken[tokenAddress] + }); + } ERC20 token = ERC20(tokenAddress); return TokenInfo({ name: token.name(), @@ -781,6 +793,7 @@ contract Bootstrap is totalDepositAmounts[msg.sender][VIRTUAL_NST_ADDRESS] += depositValue; withdrawableAmounts[msg.sender][VIRTUAL_NST_ADDRESS] += depositValue; depositsByToken[VIRTUAL_NST_ADDRESS] += depositValue; + stakerToPubkeys[msg.sender].push(validatorContainer.getPubkey()); emit DepositResult(true, VIRTUAL_NST_ADDRESS, msg.sender, depositValue); } @@ -811,4 +824,13 @@ contract Bootstrap is capsule.withdrawNonBeaconChainETHBalance(recipient, amountToWithdraw); } + /// @notice Returns the number of pubkeys (across all validators) deposited + /// by a staker. The deposit must include deposit + verification for inclusion + /// into the beacon chain. + /// @param stakerAddress the address of the staker. + /// @return the number of pubkeys deposited by the staker. + function getPubkeysCount(address stakerAddress) external view returns (uint256) { + return stakerToPubkeys[stakerAddress].length; + } + } diff --git a/src/libraries/NetworkConstants.sol b/src/libraries/NetworkConstants.sol index 56044cee..5161c3ed 100644 --- a/src/libraries/NetworkConstants.sol +++ b/src/libraries/NetworkConstants.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// solhint-disable max-line-length pragma solidity ^0.8.0; import {NetworkParams} from "../interfaces/INetworkConfig.sol"; diff --git a/src/storage/BootstrapStorage.sol b/src/storage/BootstrapStorage.sol index 6b1cb635..60c4cf16 100644 --- a/src/storage/BootstrapStorage.sol +++ b/src/storage/BootstrapStorage.sol @@ -177,6 +177,10 @@ contract BootstrapStorage is GatewayStorage { /// contracts and we put it after __gap to maintain the storage layout compatible with deployed contracts. mapping(address owner => IExoCapsule capsule) public ownerToCapsule; + /// @notice Mapping of staker addresses to their corresponding public keys. + /// @dev Maps staker addresses to their corresponding public keys used on the beacon chain. + mapping(address staker => bytes32[]) public stakerToPubkeys; + /* -------------------------------------------------------------------------- */ /* Events */ /* -------------------------------------------------------------------------- */ diff --git a/src/storage/ExoCapsuleStorage.sol b/src/storage/ExoCapsuleStorage.sol index c8d6f71b..50ff8281 100644 --- a/src/storage/ExoCapsuleStorage.sol +++ b/src/storage/ExoCapsuleStorage.sol @@ -58,7 +58,7 @@ contract ExoCapsuleStorage { /// @notice The address of the NetworkConfig contract. /// @dev If it is set to the 0 address, the NetworkConstants library is used instead. - address public immutable networkConfig; + address public immutable NETWORK_CONFIG; /// @notice the amount of execution layer ETH in this contract that is staked in(i.e. withdrawn from the Beacon /// Chain but not from Exocore) @@ -93,42 +93,42 @@ contract ExoCapsuleStorage { /// @notice Sets the network configuration contract address for the ExoCapsule contract. /// @param networkConfig_ The address of the NetworkConfig contract. constructor(address networkConfig_) { - networkConfig = networkConfig_; + NETWORK_CONFIG = networkConfig_; } /// @dev Gets the deneb hard fork timestamp, either from the NetworkConfig contract or the NetworkConstants library. function getDenebHardForkTimestamp() internal view returns (uint256) { - if (networkConfig == address(0)) { + if (NETWORK_CONFIG == address(0)) { return NetworkConstants.getDenebHardForkTimestamp(); } else { - return INetworkConfig(networkConfig).getDenebHardForkTimestamp(); + return INetworkConfig(NETWORK_CONFIG).getDenebHardForkTimestamp(); } } /// @dev Gets the slots per epoch, either from the NetworkConfig contract or the NetworkConstants library. function getSlotsPerEpoch() internal view returns (uint64) { - if (networkConfig == address(0)) { + if (NETWORK_CONFIG == address(0)) { return NetworkConstants.getSlotsPerEpoch(); } else { - return INetworkConfig(networkConfig).getSlotsPerEpoch(); + return INetworkConfig(NETWORK_CONFIG).getSlotsPerEpoch(); } } /// @dev Gets the seconds per slot, either from the NetworkConfig contract or the NetworkConstants library. function getSecondsPerEpoch() internal view returns (uint64) { - if (networkConfig == address(0)) { + if (NETWORK_CONFIG == address(0)) { return NetworkConstants.getSecondsPerEpoch(); } else { - return INetworkConfig(networkConfig).getSecondsPerEpoch(); + return INetworkConfig(NETWORK_CONFIG).getSecondsPerEpoch(); } } /// @dev Gets the beacon genesis timestamp, either from the NetworkConfig contract or the NetworkConstants library. function getBeaconGenesisTimestamp() internal view returns (uint256) { - if (networkConfig == address(0)) { + if (NETWORK_CONFIG == address(0)) { return NetworkConstants.getBeaconGenesisTimestamp(); } else { - return INetworkConfig(networkConfig).getBeaconGenesisTimestamp(); + return INetworkConfig(NETWORK_CONFIG).getBeaconGenesisTimestamp(); } } diff --git a/test/foundry/unit/NetworkConfig.t.sol b/test/foundry/unit/NetworkConfig.t.sol index fc2de668..89affbf1 100644 --- a/test/foundry/unit/NetworkConfig.t.sol +++ b/test/foundry/unit/NetworkConfig.t.sol @@ -64,7 +64,7 @@ contract NetworkConfigTest is Test { function testRevertUnsupportedChainId() public { // Change the chain ID to something other than 31337 vm.chainId(1); - vm.expectRevert("unsupported network"); + vm.expectRevert("only the 31337 chain ID is supported for integration tests"); new NetworkConfig( DEPOSIT_CONTRACT_ADDRESS, DENEB_TIMESTAMP, SLOTS_PER_EPOCH, SECONDS_PER_SLOT, BEACON_GENESIS_TIMESTAMP );