From e21b86a5f3ca9980be6b83bf525b6b5b11caab59 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 7 Feb 2024 11:06:19 +0100 Subject: [PATCH 01/41] Sepolia deposit contract (interface for tests) --- contracts/0.6.11/sepolia_deposit_contract.sol | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 contracts/0.6.11/sepolia_deposit_contract.sol diff --git a/contracts/0.6.11/sepolia_deposit_contract.sol b/contracts/0.6.11/sepolia_deposit_contract.sol new file mode 100644 index 000000000..d36143154 --- /dev/null +++ b/contracts/0.6.11/sepolia_deposit_contract.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +// solhint-disable-next-line lido/fixed-compiler-version +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; + + +// ======================================= WARNING ======================================== +// +// WARNING: This is an UNOFFICAL TESTNET-ONLY deposit contract HACK. +// *No security garantuees, at all*. Merely used for random permissioned (not verified) testnets. +// +// ================================================================================================ + + + + +// This interface is designed to be compatible with the Vyper version. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +interface IDepositContract { + /// @notice A processed deposit event. + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable; + + /// @notice Query the current deposit root hash. + /// @return The deposit root hash. + function get_deposit_root() external view returns (bytes32); + + /// @notice Query the current deposit count. + /// @return The deposit count encoded as a little endian 64-bit number. + function get_deposit_count() external view returns (bytes memory); +} + +// Based on official specification in https://eips.ethereum.org/EIPS/eip-165 +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceId The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceId` and + /// `interfaceId` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceId) external pure returns (bool); +} + +// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. +// It tries to stay as close as possible to the original source code. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +contract SepoliaDepositContract is IDepositContract, ERC165, IERC20, ERC20, ERC20Burnable { + uint constant GWEI = 1e9; + + uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; + // NOTE: this also ensures `deposit_count` will fit into 64-bits + uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; + uint256 deposit_count; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; + + address payable creator; + + constructor () public ERC20("Testnet deposit token", "TDEP") { + // Compute hashes in empty sparse Merkle tree + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) + zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); + // No decimals: 1 token = 1 deposit. + _setupDecimals(0); + // Mint 1 million deposit tokens for the contract creator. + _mint(msg.sender, 1e9); + creator = msg.sender; + } + + // Public function to send all available funds back to contract creator + function drain() public { + creator.transfer(address(this).balance); + } + + function adminBurn(address account, uint256 amount) public { + require(msg.sender == creator); + _burn(account, amount); + } + + /// @notice Query the current deposit root hash. + /// @return The deposit root hash. + function get_deposit_root() override external view returns (bytes32) { + bytes32 node; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) + node = sha256(abi.encodePacked(branch[height], node)); + else + node = sha256(abi.encodePacked(node, zero_hashes[height])); + size /= 2; + } + return sha256(abi.encodePacked( + node, + to_little_endian_64(uint64(deposit_count)), + bytes24(0) + )); + } + + /// @notice Query the current deposit count. + /// @return The deposit count encoded as a little endian 64-bit number. + function get_deposit_count() override external view returns (bytes memory) { + return to_little_endian_64(uint64(deposit_count)); + } + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) override external payable { + // Extended ABI length checks since dynamic types are used. + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); + require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); + require(signature.length == 96, "DepositContract: invalid signature length"); + + // Send back any goerli value they try to send us (some deposit tooling does not allow to change the amount) + msg.sender.transfer(msg.value); + + // WARNING: disabled amount check. + // // Check deposit amount + // require(msg.value >= 1 ether, "DepositContract: deposit value too low"); + // require(msg.value % GWEI == 0, "DepositContract: deposit value not multiple of gwei"); + // uint deposit_amount = msg.value / GWEI; + // require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); + + // INSTEAD, burn 1 token (also checks the token balance) + _burn(msg.sender, 1); + + // Emit `DepositEvent` log + // 32 ETH (in GWEI) hardcoded + bytes memory amount = to_little_endian_64(uint64(32e9)); + emit DepositEvent( + pubkey, + withdrawal_credentials, + amount, + signature, + to_little_endian_64(uint64(deposit_count)) + ); + + // Compute deposit data root (`DepositData` hash tree root) + bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); + bytes32 signature_root = sha256(abi.encodePacked( + sha256(abi.encodePacked(signature[:64])), + sha256(abi.encodePacked(signature[64:], bytes32(0))) + )); + bytes32 node = sha256(abi.encodePacked( + sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), + sha256(abi.encodePacked(amount, bytes24(0), signature_root)) + )); + + // Verify computed and expected deposit data roots match + require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); + + // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) + require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); + + // Add deposit data root to Merkle tree (update a single `branch` node) + deposit_count += 1; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) { + branch[height] = node; + return; + } + node = sha256(abi.encodePacked(branch[height], node)); + size /= 2; + } + // As the loop should always end prematurely with the `return` statement, + // this code should be unreachable. We assert `false` just to be safe. + assert(false); + } + + function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { + return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId || interfaceId == type(IERC20).interfaceId; + } + + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { + ret = new bytes(8); + bytes8 bytesValue = bytes8(value); + // Byteswapping during copying to bytes. + ret[0] = bytesValue[7]; + ret[1] = bytesValue[6]; + ret[2] = bytesValue[5]; + ret[3] = bytesValue[4]; + ret[4] = bytesValue[3]; + ret[5] = bytesValue[2]; + ret[6] = bytesValue[1]; + ret[7] = bytesValue[0]; + } +} From 876a3b0541bda81855a794204a8c47be5056b1bd Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 7 Feb 2024 12:29:49 +0100 Subject: [PATCH 02/41] Add basic test for Sepolia deposit --- test/0.8.9/sepolia-deposit-adapter.test.js | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/0.8.9/sepolia-deposit-adapter.test.js diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js new file mode 100644 index 000000000..fd7578c76 --- /dev/null +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -0,0 +1,48 @@ +const { artifacts, contract, ethers } = require('hardhat') +const { assert } = require('../helpers/assert') + +const { EvmSnapshot } = require('../helpers/blockchain') + +const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') + +contract('SepoliaDepositAdapter impl', ([deployer]) => { + let depositAdapter + let snapshot + const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' + const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' + const bepoliaTokenHolder = '0x388Ea662EF2c223eC0B047D41Bf3c0f362142ad5' + const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' + + before('deploy lido with dao', async () => { + depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) + const dna = await depositAdapter.TEST_VALUE() + console.log(dna) + + const bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) + const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + + const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) + await impersonatedSigner.sendTransaction({ to: EOAddress, value: ethers.utils.parseEther('2.0') }) + + const bepoliaTokensToTransfer = 1 + bepoliaToken.connect(impersonatedSigner).transfer(EOAddress, bepoliaTokensToTransfer) + + const bepoliaOwnTokens = await bepoliaToken.balanceOf(EOAddress) + assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) + + const bepoliaEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) + console.log(bepoliaEndBalance) + + snapshot = new EvmSnapshot(ethers.provider) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + describe('SepoliaDepositAdapter Logic', () => { + it(`state after deployment`, async () => {}) + }) +}) From 7e1d8e2a82a9a0553d56482c83e1b37b68fea47b Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 7 Feb 2024 15:11:54 +0100 Subject: [PATCH 03/41] Put simple deployment test --- test/0.8.9/sepolia-deployment.test.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/0.8.9/sepolia-deployment.test.js diff --git a/test/0.8.9/sepolia-deployment.test.js b/test/0.8.9/sepolia-deployment.test.js new file mode 100644 index 000000000..4a35ac8d8 --- /dev/null +++ b/test/0.8.9/sepolia-deployment.test.js @@ -0,0 +1,27 @@ +const { artifacts, contract, ethers } = require('hardhat') + +const { EvmSnapshot } = require('../helpers/blockchain') + +const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') + +contract('SepoliaDepositAdapter', ([deployer]) => { + let depositAdapter + let snapshot + + before('deploy lido with dao', async () => { + depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) + const dna = await depositAdapter.TEST_VALUE() + console.log(dna) + + snapshot = new EvmSnapshot(ethers.provider) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + describe('SepoliaDepositAdapter Logic', () => { + it(`state after deployment`, async () => {}) + }) +}) From bf3540aa6201fc4a9f9a9ff76ac03886f93d6306 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 7 Feb 2024 15:22:27 +0100 Subject: [PATCH 04/41] Basic deposit adapter implementation --- contracts/0.8.9/SepoliaDepositAdapter.sol | 41 +++++++++++++++++++--- test/0.8.9/sepolia-deposit-adapter.test.js | 12 ++++--- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index cd43cf968..00cf5a8c0 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -5,23 +5,54 @@ pragma solidity 0.8.9; -contract SepoliaDepositAdapter { +abstract contract SepoliaDepositInterface { - uint public constant TEST_VALUE = 16; - address public immutable depositContract; + function get_deposit_root() external virtual view returns (bytes32); + + function get_deposit_count() external virtual view returns (bytes memory); function deposit( bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root - ) external payable { - } + ) external virtual payable; + +} + +contract SepoliaDepositAdapter { + + uint public constant VERSION = 2; + address public immutable depositContract; + address payable public creator; constructor(address _deposit_contract) { depositContract = _deposit_contract; + creator = payable(msg.sender); } + function get_deposit_root() external view returns (bytes32) { + SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); + return origContract.get_deposit_root(); + } + function get_deposit_count() external view returns (bytes memory) { + SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); + return origContract.get_deposit_count(); + } + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable { + SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); + origContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); + } + + // Public function to send all available funds back to contract creator + function drain() public { + creator.transfer(address(this).balance); + } } diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index fd7578c76..b6b8ef5c1 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -8,15 +8,17 @@ const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') contract('SepoliaDepositAdapter impl', ([deployer]) => { let depositAdapter let snapshot - const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' + // const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' const bepoliaTokenHolder = '0x388Ea662EF2c223eC0B047D41Bf3c0f362142ad5' const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' before('deploy lido with dao', async () => { - depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) - const dna = await depositAdapter.TEST_VALUE() - console.log(dna) + // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) + + depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) + const testVersion = await depositAdapter.VERSION() + assert.equals(testVersion, 2) const bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) @@ -34,6 +36,8 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) console.log(bepoliaEndBalance) + depositAdapter.connect(impersonatedSigner).deposit(bepoliaTokensToTransfer) + snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() }) From 3e89caf84043e418d8afc4c3e23cd1f9f34c4612 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 8 Feb 2024 10:00:35 +0100 Subject: [PATCH 05/41] Some experiments over adapter tests --- contracts/0.8.9/SepoliaDepositAdapter.sol | 34 ++++++--------- test/0.8.9/sepolia-deposit-adapter.test.js | 49 ++++++++++++++++++---- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 00cf5a8c0..4f1623338 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -1,53 +1,43 @@ // SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 -/* See contracts/COMPILERS.md */ -pragma solidity 0.8.9; - - -abstract contract SepoliaDepositInterface { - - function get_deposit_root() external virtual view returns (bytes32); +import "../0.6.11/sepolia_deposit_contract.sol"; - function get_deposit_count() external virtual view returns (bytes memory); - - function deposit( - bytes calldata pubkey, - bytes calldata withdrawal_credentials, - bytes calldata signature, - bytes32 deposit_data_root - ) external virtual payable; +/* See contracts/COMPILERS.md */ +// pragma solidity 0.8.9; +pragma solidity >=0.6.8 <0.9.0; -} contract SepoliaDepositAdapter { uint public constant VERSION = 2; - address public immutable depositContract; + SepoliaDepositContract origContract; + address payable public creator; - constructor(address _deposit_contract) { - depositContract = _deposit_contract; + constructor(address _deposit_contract) public { + origContract = SepoliaDepositContract(_deposit_contract); creator = payable(msg.sender); } function get_deposit_root() external view returns (bytes32) { - SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); return origContract.get_deposit_root(); } function get_deposit_count() external view returns (bytes memory) { - SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); return origContract.get_deposit_count(); } + function test() external view returns (string memory) { + return origContract.name(); + } + function deposit( bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root ) external payable { - SepoliaDepositInterface origContract = SepoliaDepositInterface(depositContract); origContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); } diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index b6b8ef5c1..70088b9e1 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -1,5 +1,6 @@ const { artifacts, contract, ethers } = require('hardhat') const { assert } = require('../helpers/assert') +const { pad, ETH, hexConcat, toBN } = require('../helpers/utils') const { EvmSnapshot } = require('../helpers/blockchain') @@ -11,32 +12,64 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { // const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' const bepoliaTokenHolder = '0x388Ea662EF2c223eC0B047D41Bf3c0f362142ad5' - const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' + // const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' before('deploy lido with dao', async () => { // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) - depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) + console.log('deployer', deployer) + // depositAdapter = await SepoliaDepositAdapter.new(deployer, [sepoliaDepositContract], { from: deployer }) + + depositAdapter = await ethers.deployContract("SepoliaDepositAdapter", [sepoliaDepositContract]); + console.log('depositAdapter address', depositAdapter.address) + // await depositAdapter.waitForDeployment(); + + const testVersion = await depositAdapter.VERSION() assert.equals(testVersion, 2) + let depositCaller = depositAdapter.address; + const bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + console.log('bepoliaStartBalance', bepoliaStartBalance) const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - await impersonatedSigner.sendTransaction({ to: EOAddress, value: ethers.utils.parseEther('2.0') }) + // await impersonatedSigner.sendTransaction({ to: depositCaller, value: ethers.utils.parseEther('5.0') }) const bepoliaTokensToTransfer = 1 - bepoliaToken.connect(impersonatedSigner).transfer(EOAddress, bepoliaTokensToTransfer) + await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, bepoliaTokensToTransfer) - const bepoliaOwnTokens = await bepoliaToken.balanceOf(EOAddress) - assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) + const bepoliaOwnTokens = await bepoliaToken.balanceOf(depositCaller) + // assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) const bepoliaEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) - assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) + // assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) console.log(bepoliaEndBalance) - depositAdapter.connect(impersonatedSigner).deposit(bepoliaTokensToTransfer) + const key = '0x90823dc2e5ab8a52a0b32883ea8451cbe4c921a42ce439f4fb306a90e9f267e463241da7274b6d44c2e4b95ddbcb0ad3' + // const withdrawalCredentials = deployer + const withdrawalCredentials = '0x005bfe00d82068a0c2a6687afaf969dad5a9c663cb492815a65d203885aaf993' + const sig = '0x802899068eb4b37c95d46869947cac42b9c65b90fcb3fde3854c93ad5737800c01e9c82e174c8ed5cc18210bd60a94ea0082a850817b1dddd4096059b6846417b05094c59d3dd7f4028ed9dff395755f9905a88015b0ed200a7ec1ed60c24922' + const dataRoot = '0x8b09ed1d0fb3b8e3bb8398c6b77ee3d8e4f67c23cb70555167310ef02b06e5f5' + // await depositAdapter.connect(impersonatedSigner) + + const bal3 = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const bal4 = await bepoliaToken.balanceOf(depositCaller) + console.log('balances before', bal3, bal4) + + const result = await depositAdapter.test() + console.log('result', result) + await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot) + + const bal1 = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const bal2 = await bepoliaToken.balanceOf(depositCaller) + console.log('balances', bal1, bal2) + + // , { + // from: EOAddress, + // value: ETH(32), + // }) snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() From d5474b34b5cb1200a2ed97c842b39ae9764722dd Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 8 Feb 2024 16:27:29 +0100 Subject: [PATCH 06/41] Simplify adapter --- contracts/0.8.9/SepoliaDepositAdapter.sol | 28 +++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 4f1623338..03b0bf143 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -1,22 +1,34 @@ // SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 -import "../0.6.11/sepolia_deposit_contract.sol"; - /* See contracts/COMPILERS.md */ -// pragma solidity 0.8.9; -pragma solidity >=0.6.8 <0.9.0; +pragma solidity 0.8.9; + +interface ISepoliaDepositContract { + + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable; + + function get_deposit_root() external view returns (bytes32); + function get_deposit_count() external view returns (bytes memory); + + function name() external view returns (string memory); +} contract SepoliaDepositAdapter { uint public constant VERSION = 2; - SepoliaDepositContract origContract; + ISepoliaDepositContract origContract; address payable public creator; - constructor(address _deposit_contract) public { - origContract = SepoliaDepositContract(_deposit_contract); + constructor(address _deposit_contract) { + origContract = ISepoliaDepositContract(_deposit_contract); creator = payable(msg.sender); } @@ -32,6 +44,8 @@ contract SepoliaDepositAdapter { return origContract.name(); } + receive() payable external {} + function deposit( bytes calldata pubkey, bytes calldata withdrawal_credentials, From 6bb847fc06758fd0f7e2eeb3b07c8f2778a07704 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 8 Feb 2024 16:32:28 +0100 Subject: [PATCH 07/41] Split tests, fix style --- test/0.8.9/sepolia-deposit-adapter.test.js | 102 ++++++++++----------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 70088b9e1..d94453f10 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -1,14 +1,12 @@ -const { artifacts, contract, ethers } = require('hardhat') +const { contract, ethers } = require('hardhat') const { assert } = require('../helpers/assert') -const { pad, ETH, hexConcat, toBN } = require('../helpers/utils') const { EvmSnapshot } = require('../helpers/blockchain') -const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') - contract('SepoliaDepositAdapter impl', ([deployer]) => { let depositAdapter let snapshot + let bepoliaToken // const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' const bepoliaTokenHolder = '0x388Ea662EF2c223eC0B047D41Bf3c0f362142ad5' @@ -16,70 +14,68 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { before('deploy lido with dao', async () => { // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) - - console.log('deployer', deployer) // depositAdapter = await SepoliaDepositAdapter.new(deployer, [sepoliaDepositContract], { from: deployer }) - - depositAdapter = await ethers.deployContract("SepoliaDepositAdapter", [sepoliaDepositContract]); + depositAdapter = await ethers.deployContract('SepoliaDepositAdapter', [sepoliaDepositContract]) console.log('depositAdapter address', depositAdapter.address) - // await depositAdapter.waitForDeployment(); - - - const testVersion = await depositAdapter.VERSION() - assert.equals(testVersion, 2) - let depositCaller = depositAdapter.address; + const depositAdapterVersion = await depositAdapter.VERSION() + assert.equals(depositAdapterVersion, 2) - const bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) - const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) - console.log('bepoliaStartBalance', bepoliaStartBalance) + bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) - const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - // await impersonatedSigner.sendTransaction({ to: depositCaller, value: ethers.utils.parseEther('5.0') }) + snapshot = new EvmSnapshot(ethers.provider) + await snapshot.make() + }) - const bepoliaTokensToTransfer = 1 - await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, bepoliaTokensToTransfer) + afterEach(async () => { + await snapshot.rollback() + }) - const bepoliaOwnTokens = await bepoliaToken.balanceOf(depositCaller) - // assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) + describe('SepoliaDepositAdapter Logic', () => { + it(`transfer Bepolia tokens`, async () => { + const depositCaller = depositAdapter.address - const bepoliaEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) - // assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) - console.log(bepoliaEndBalance) + const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + console.log('bepoliaStartBalance', bepoliaStartBalance) - const key = '0x90823dc2e5ab8a52a0b32883ea8451cbe4c921a42ce439f4fb306a90e9f267e463241da7274b6d44c2e4b95ddbcb0ad3' - // const withdrawalCredentials = deployer - const withdrawalCredentials = '0x005bfe00d82068a0c2a6687afaf969dad5a9c663cb492815a65d203885aaf993' - const sig = '0x802899068eb4b37c95d46869947cac42b9c65b90fcb3fde3854c93ad5737800c01e9c82e174c8ed5cc18210bd60a94ea0082a850817b1dddd4096059b6846417b05094c59d3dd7f4028ed9dff395755f9905a88015b0ed200a7ec1ed60c24922' - const dataRoot = '0x8b09ed1d0fb3b8e3bb8398c6b77ee3d8e4f67c23cb70555167310ef02b06e5f5' - // await depositAdapter.connect(impersonatedSigner) + const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - const bal3 = await bepoliaToken.balanceOf(bepoliaTokenHolder) - const bal4 = await bepoliaToken.balanceOf(depositCaller) - console.log('balances before', bal3, bal4) + const bepoliaTokensToTransfer = 2 + await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, bepoliaTokensToTransfer) - const result = await depositAdapter.test() - console.log('result', result) - await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot) + const bepoliaOwnTokens = await bepoliaToken.balanceOf(depositCaller) + assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) - const bal1 = await bepoliaToken.balanceOf(bepoliaTokenHolder) - const bal2 = await bepoliaToken.balanceOf(depositCaller) - console.log('balances', bal1, bal2) + const bepoliaEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) + console.log('bepoliaEndBalance', bepoliaEndBalance) + }) - // , { - // from: EOAddress, - // value: ETH(32), - // }) + it(`call deposit on Adapter`, async () => { + const key = '0x90823dc2e5ab8a52a0b32883ea8451cbe4c921a42ce439f4fb306a90e9f267e463241da7274b6d44c2e4b95ddbcb0ad3' + const withdrawalCredentials = '0x005bfe00d82068a0c2a6687afaf969dad5a9c663cb492815a65d203885aaf993' + const sig = + '0x802899068eb4b37c95d46869947cac42b9c65b90fcb3fde3854c93ad5737800c01e9c82e174c8ed5cc18210bd60a94ea0082a850817b1dddd4096059b6846417b05094c59d3dd7f4028ed9dff395755f9905a88015b0ed200a7ec1ed60c24922' + const dataRoot = '0x8b09ed1d0fb3b8e3bb8398c6b77ee3d8e4f67c23cb70555167310ef02b06e5f5' - snapshot = new EvmSnapshot(ethers.provider) - await snapshot.make() - }) + const depositCaller = depositAdapter.address - afterEach(async () => { - await snapshot.rollback() - }) + const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - describe('SepoliaDepositAdapter Logic', () => { - it(`state after deployment`, async () => {}) + await depositAdapter.connect(impersonatedSigner) + await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, 1) + + const bal3 = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const bal4 = await bepoliaToken.balanceOf(depositCaller) + console.log('balances before', bal3, bal4) + + const result = await depositAdapter.test() + console.log('result', result) + await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot) + + const bal1 = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const bal2 = await bepoliaToken.balanceOf(depositCaller) + console.log('balances', bal1, bal2) + }) }) }) From 3eeafd1297b41d78ff5fc041688fbac91853010e Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 8 Feb 2024 16:34:04 +0100 Subject: [PATCH 08/41] Improve contract readability --- contracts/0.8.9/SepoliaDepositAdapter.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 03b0bf143..172b56990 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -23,28 +23,28 @@ interface ISepoliaDepositContract { contract SepoliaDepositAdapter { uint public constant VERSION = 2; - ISepoliaDepositContract origContract; + ISepoliaDepositContract public originalContract; address payable public creator; constructor(address _deposit_contract) { - origContract = ISepoliaDepositContract(_deposit_contract); + originalContract = ISepoliaDepositContract(_deposit_contract); creator = payable(msg.sender); } function get_deposit_root() external view returns (bytes32) { - return origContract.get_deposit_root(); + return originalContract.get_deposit_root(); } function get_deposit_count() external view returns (bytes memory) { - return origContract.get_deposit_count(); + return originalContract.get_deposit_count(); } function test() external view returns (string memory) { - return origContract.name(); + return originalContract.name(); } - receive() payable external {} + receive() external payable {} function deposit( bytes calldata pubkey, @@ -52,7 +52,7 @@ contract SepoliaDepositAdapter { bytes calldata signature, bytes32 deposit_data_root ) external payable { - origContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); + originalContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); } // Public function to send all available funds back to contract creator From 52df1f9c473cad7bba70ce2e876f144bd81149f9 Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 12 Feb 2024 17:19:13 +0100 Subject: [PATCH 09/41] Remove Sepolia deposit contract code, change all interaction to interface --- contracts/0.6.11/sepolia_deposit_contract.sol | 221 ------------------ contracts/0.8.9/SepoliaDepositAdapter.sol | 5 +- test/0.8.9/sepolia-deposit-adapter.test.js | 2 +- 3 files changed, 5 insertions(+), 223 deletions(-) delete mode 100644 contracts/0.6.11/sepolia_deposit_contract.sol diff --git a/contracts/0.6.11/sepolia_deposit_contract.sol b/contracts/0.6.11/sepolia_deposit_contract.sol deleted file mode 100644 index d36143154..000000000 --- a/contracts/0.6.11/sepolia_deposit_contract.sol +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// solhint-disable-next-line lido/fixed-compiler-version -pragma solidity ^0.6.8; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; - - -// ======================================= WARNING ======================================== -// -// WARNING: This is an UNOFFICAL TESTNET-ONLY deposit contract HACK. -// *No security garantuees, at all*. Merely used for random permissioned (not verified) testnets. -// -// ================================================================================================ - - - - -// This interface is designed to be compatible with the Vyper version. -/// @notice This is the Ethereum 2.0 deposit contract interface. -/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs -interface IDepositContract { - /// @notice A processed deposit event. - event DepositEvent( - bytes pubkey, - bytes withdrawal_credentials, - bytes amount, - bytes signature, - bytes index - ); - - /// @notice Submit a Phase 0 DepositData object. - /// @param pubkey A BLS12-381 public key. - /// @param withdrawal_credentials Commitment to a public key for withdrawals. - /// @param signature A BLS12-381 signature. - /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. - /// Used as a protection against malformed input. - function deposit( - bytes calldata pubkey, - bytes calldata withdrawal_credentials, - bytes calldata signature, - bytes32 deposit_data_root - ) external payable; - - /// @notice Query the current deposit root hash. - /// @return The deposit root hash. - function get_deposit_root() external view returns (bytes32); - - /// @notice Query the current deposit count. - /// @return The deposit count encoded as a little endian 64-bit number. - function get_deposit_count() external view returns (bytes memory); -} - -// Based on official specification in https://eips.ethereum.org/EIPS/eip-165 -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceId The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceId` and - /// `interfaceId` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceId) external pure returns (bool); -} - -// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. -// It tries to stay as close as possible to the original source code. -/// @notice This is the Ethereum 2.0 deposit contract interface. -/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs -contract SepoliaDepositContract is IDepositContract, ERC165, IERC20, ERC20, ERC20Burnable { - uint constant GWEI = 1e9; - - uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; - // NOTE: this also ensures `deposit_count` will fit into 64-bits - uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; - - bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; - uint256 deposit_count; - - bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; - - address payable creator; - - constructor () public ERC20("Testnet deposit token", "TDEP") { - // Compute hashes in empty sparse Merkle tree - for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) - zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); - // No decimals: 1 token = 1 deposit. - _setupDecimals(0); - // Mint 1 million deposit tokens for the contract creator. - _mint(msg.sender, 1e9); - creator = msg.sender; - } - - // Public function to send all available funds back to contract creator - function drain() public { - creator.transfer(address(this).balance); - } - - function adminBurn(address account, uint256 amount) public { - require(msg.sender == creator); - _burn(account, amount); - } - - /// @notice Query the current deposit root hash. - /// @return The deposit root hash. - function get_deposit_root() override external view returns (bytes32) { - bytes32 node; - uint size = deposit_count; - for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { - if ((size & 1) == 1) - node = sha256(abi.encodePacked(branch[height], node)); - else - node = sha256(abi.encodePacked(node, zero_hashes[height])); - size /= 2; - } - return sha256(abi.encodePacked( - node, - to_little_endian_64(uint64(deposit_count)), - bytes24(0) - )); - } - - /// @notice Query the current deposit count. - /// @return The deposit count encoded as a little endian 64-bit number. - function get_deposit_count() override external view returns (bytes memory) { - return to_little_endian_64(uint64(deposit_count)); - } - - /// @notice Submit a Phase 0 DepositData object. - /// @param pubkey A BLS12-381 public key. - /// @param withdrawal_credentials Commitment to a public key for withdrawals. - /// @param signature A BLS12-381 signature. - /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. - /// Used as a protection against malformed input. - function deposit( - bytes calldata pubkey, - bytes calldata withdrawal_credentials, - bytes calldata signature, - bytes32 deposit_data_root - ) override external payable { - // Extended ABI length checks since dynamic types are used. - require(pubkey.length == 48, "DepositContract: invalid pubkey length"); - require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); - require(signature.length == 96, "DepositContract: invalid signature length"); - - // Send back any goerli value they try to send us (some deposit tooling does not allow to change the amount) - msg.sender.transfer(msg.value); - - // WARNING: disabled amount check. - // // Check deposit amount - // require(msg.value >= 1 ether, "DepositContract: deposit value too low"); - // require(msg.value % GWEI == 0, "DepositContract: deposit value not multiple of gwei"); - // uint deposit_amount = msg.value / GWEI; - // require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); - - // INSTEAD, burn 1 token (also checks the token balance) - _burn(msg.sender, 1); - - // Emit `DepositEvent` log - // 32 ETH (in GWEI) hardcoded - bytes memory amount = to_little_endian_64(uint64(32e9)); - emit DepositEvent( - pubkey, - withdrawal_credentials, - amount, - signature, - to_little_endian_64(uint64(deposit_count)) - ); - - // Compute deposit data root (`DepositData` hash tree root) - bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); - bytes32 signature_root = sha256(abi.encodePacked( - sha256(abi.encodePacked(signature[:64])), - sha256(abi.encodePacked(signature[64:], bytes32(0))) - )); - bytes32 node = sha256(abi.encodePacked( - sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), - sha256(abi.encodePacked(amount, bytes24(0), signature_root)) - )); - - // Verify computed and expected deposit data roots match - require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); - - // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) - require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); - - // Add deposit data root to Merkle tree (update a single `branch` node) - deposit_count += 1; - uint size = deposit_count; - for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { - if ((size & 1) == 1) { - branch[height] = node; - return; - } - node = sha256(abi.encodePacked(branch[height], node)); - size /= 2; - } - // As the loop should always end prematurely with the `return` statement, - // this code should be unreachable. We assert `false` just to be safe. - assert(false); - } - - function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { - return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId || interfaceId == type(IERC20).interfaceId; - } - - function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { - ret = new bytes(8); - bytes8 bytesValue = bytes8(value); - // Byteswapping during copying to bytes. - ret[0] = bytesValue[7]; - ret[1] = bytesValue[6]; - ret[2] = bytesValue[5]; - ret[3] = bytesValue[4]; - ret[4] = bytesValue[3]; - ret[5] = bytesValue[2]; - ret[6] = bytesValue[1]; - ret[7] = bytesValue[0]; - } -} diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 172b56990..953b29a00 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -4,7 +4,10 @@ /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; -interface ISepoliaDepositContract { +import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; + +// Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol +interface ISepoliaDepositContract is IERC20 { function deposit( bytes calldata pubkey, diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index d94453f10..51a1d92d7 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -21,7 +21,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { const depositAdapterVersion = await depositAdapter.VERSION() assert.equals(depositAdapterVersion, 2) - bepoliaToken = await ethers.getContractAt('SepoliaDepositContract', sepoliaDepositContract) + bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContract) snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() From 287e8680383615e15f7fb5d04034e496c59bd2dc Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 12 Feb 2024 17:19:41 +0100 Subject: [PATCH 10/41] Fix warnings --- contracts/0.8.9/SepoliaDepositAdapter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 953b29a00..ee6e7ce23 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -28,7 +28,7 @@ contract SepoliaDepositAdapter { uint public constant VERSION = 2; ISepoliaDepositContract public originalContract; - address payable public creator; + address payable public immutable creator; constructor(address _deposit_contract) { originalContract = ISepoliaDepositContract(_deposit_contract); @@ -47,7 +47,7 @@ contract SepoliaDepositAdapter { return originalContract.name(); } - receive() external payable {} + receive() external payable {} function deposit( bytes calldata pubkey, From 04d6b7fdb4afabb2f717e4c3068d637fd333b4b0 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 13 Feb 2024 15:53:54 +0100 Subject: [PATCH 11/41] Add immutable --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index ee6e7ce23..a1e7c9439 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -26,7 +26,7 @@ interface ISepoliaDepositContract is IERC20 { contract SepoliaDepositAdapter { uint public constant VERSION = 2; - ISepoliaDepositContract public originalContract; + ISepoliaDepositContract public immutable originalContract; address payable public immutable creator; From cabc5cd70a23be554e3976e3db349c5f06090d6f Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 13 Feb 2024 17:25:18 +0100 Subject: [PATCH 12/41] Add Ownable mix to Deposit --- contracts/0.8.9/SepoliaDepositAdapter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index a1e7c9439..83cfc0272 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4.4/access/Ownable.sol"; // Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol interface ISepoliaDepositContract is IERC20 { @@ -23,7 +24,7 @@ interface ISepoliaDepositContract is IERC20 { function name() external view returns (string memory); } -contract SepoliaDepositAdapter { +contract SepoliaDepositAdapter is Ownable { uint public constant VERSION = 2; ISepoliaDepositContract public immutable originalContract; From 2b905b29633543b8e5936b027e8abb6cb40f3ee2 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 13 Feb 2024 17:27:48 +0100 Subject: [PATCH 13/41] Implement drain Bepolia logic and send back ether to contract owner --- contracts/0.8.9/SepoliaDepositAdapter.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 83cfc0272..8b4b0e88a 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -48,7 +48,15 @@ contract SepoliaDepositAdapter is Ownable { return originalContract.name(); } - receive() external payable {} + receive() external payable { + address payable owner = payable(owner()); + owner.transfer(msg.value); + } + + function drainBepolia() external onlyOwner { + uint bepoliaOwnTokens = originalContract.balanceOf(address(this)); + originalContract.transfer(owner(), bepoliaOwnTokens); + } function deposit( bytes calldata pubkey, From 53861a226dd056fe73794d6b43b50c6034cc0bda Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 13 Feb 2024 17:28:47 +0100 Subject: [PATCH 14/41] Remove general drain func as it now send eth to owner automatically --- contracts/0.8.9/SepoliaDepositAdapter.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 8b4b0e88a..e41773ce2 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -29,11 +29,8 @@ contract SepoliaDepositAdapter is Ownable { uint public constant VERSION = 2; ISepoliaDepositContract public immutable originalContract; - address payable public immutable creator; - constructor(address _deposit_contract) { originalContract = ISepoliaDepositContract(_deposit_contract); - creator = payable(msg.sender); } function get_deposit_root() external view returns (bytes32) { @@ -66,9 +63,4 @@ contract SepoliaDepositAdapter is Ownable { ) external payable { originalContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); } - - // Public function to send all available funds back to contract creator - function drain() public { - creator.transfer(address(this).balance); - } } From da8ea29e986390e99d893f306f08234c66cb18a1 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 16:00:05 +0100 Subject: [PATCH 15/41] Improve adapter logic --- contracts/0.8.9/SepoliaDepositAdapter.sol | 27 ++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index e41773ce2..3eaff9b6f 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -7,6 +7,8 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/access/Ownable.sol"; +import "hardhat/console.sol"; + // Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol interface ISepoliaDepositContract is IERC20 { @@ -46,8 +48,22 @@ contract SepoliaDepositAdapter is Ownable { } receive() external payable { - address payable owner = payable(owner()); - owner.transfer(msg.value); + uint ownTokens = address(this).balance; + console.log( + "Receive %s tokens from %s (own %d)", + msg.value, + msg.sender, + ownTokens + ); + // address payable sendTo = payable(owner()); + // address payable sendTo = payable(address(0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5)); + // sendTo.transfer(ownTokens); + } + + function drain() external onlyOwner { + uint ownTokens = address(this).balance; + address payable _owner = payable(owner()); + _owner.transfer(ownTokens); } function drainBepolia() external onlyOwner { @@ -61,6 +77,11 @@ contract SepoliaDepositAdapter is Ownable { bytes calldata signature, bytes32 deposit_data_root ) external payable { - originalContract.deposit(pubkey, withdrawal_credentials, signature, deposit_data_root); + console.log( + "Deposit with %s ETH from %s", + msg.value, + msg.sender + ); + originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root); } } From c60e04a517254e6a67558edb47f0c89464e92490 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 16:01:39 +0100 Subject: [PATCH 16/41] Improve tests --- test/0.8.9/sepolia-deposit-adapter.test.js | 99 ++++++++++++++++------ 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 51a1d92d7..57ce9965f 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -9,14 +9,15 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { let bepoliaToken // const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' - const bepoliaTokenHolder = '0x388Ea662EF2c223eC0B047D41Bf3c0f362142ad5' - // const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' + const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' + const bepoliaTokenHolder = EOAddress + // const log = console.log + const log = () => {} before('deploy lido with dao', async () => { // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) - // depositAdapter = await SepoliaDepositAdapter.new(deployer, [sepoliaDepositContract], { from: deployer }) depositAdapter = await ethers.deployContract('SepoliaDepositAdapter', [sepoliaDepositContract]) - console.log('depositAdapter address', depositAdapter.address) + log('depositAdapter address', depositAdapter.address) const depositAdapterVersion = await depositAdapter.VERSION() assert.equals(depositAdapterVersion, 2) @@ -33,22 +34,29 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { describe('SepoliaDepositAdapter Logic', () => { it(`transfer Bepolia tokens`, async () => { - const depositCaller = depositAdapter.address + const adapterAddr = depositAdapter.address + const BEPOLIA_TO_TRANSFER = 2 + const bepoliaHolderInitialBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - const bepoliaStartBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) - console.log('bepoliaStartBalance', bepoliaStartBalance) + log('bepoliaHolderInitialBalance', bepoliaHolderInitialBalance) + await bepoliaToken.connect(impersonatedSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER) - const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) + assert.equals(await bepoliaToken.balanceOf(adapterAddr), BEPOLIA_TO_TRANSFER) - const bepoliaTokensToTransfer = 2 - await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, bepoliaTokensToTransfer) + const bepoliaHolderEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + assert.equals(bepoliaHolderEndBalance, bepoliaHolderInitialBalance - BEPOLIA_TO_TRANSFER) + log('bepoliaHolderEndBalance', bepoliaHolderEndBalance) - const bepoliaOwnTokens = await bepoliaToken.balanceOf(depositCaller) - assert.equals(bepoliaOwnTokens, bepoliaTokensToTransfer) + // Drain Bepolia tokens + await depositAdapter.drainBepolia() - const bepoliaEndBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) - assert.equals(bepoliaEndBalance, bepoliaStartBalance - bepoliaTokensToTransfer) - console.log('bepoliaEndBalance', bepoliaEndBalance) + const bepoliaTokensOnAdapter = await bepoliaToken.balanceOf(adapterAddr) + assert.equals(bepoliaTokensOnAdapter, 0) + + const [owner] = await ethers.getSigners() + const bepoliaTokenHolderEnd = await bepoliaToken.balanceOf(owner.address) + assert.equals(bepoliaTokenHolderEnd, BEPOLIA_TO_TRANSFER) }) it(`call deposit on Adapter`, async () => { @@ -58,24 +66,59 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { '0x802899068eb4b37c95d46869947cac42b9c65b90fcb3fde3854c93ad5737800c01e9c82e174c8ed5cc18210bd60a94ea0082a850817b1dddd4096059b6846417b05094c59d3dd7f4028ed9dff395755f9905a88015b0ed200a7ec1ed60c24922' const dataRoot = '0x8b09ed1d0fb3b8e3bb8398c6b77ee3d8e4f67c23cb70555167310ef02b06e5f5' - const depositCaller = depositAdapter.address + const adapterAddr = depositAdapter.address - const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) + const balance0ETH = await ethers.provider.getBalance(adapterAddr) + assert.equals(balance0ETH, 0) + const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) await depositAdapter.connect(impersonatedSigner) - await bepoliaToken.connect(impersonatedSigner).transfer(depositCaller, 1) - - const bal3 = await bepoliaToken.balanceOf(bepoliaTokenHolder) - const bal4 = await bepoliaToken.balanceOf(depositCaller) - console.log('balances before', bal3, bal4) + // Transfer 1 Bepolia token to depositCaller + await bepoliaToken.connect(impersonatedSigner).transfer(adapterAddr, 1) + + const [owner] = await ethers.getSigners() + // log('owner', owner.address); + // await owner.sendTransaction({ + // to: adapterAddr, + // value: ethers.utils.parseEther("10.0"), // Sends exactly 1.0 ether + // }); + + const bepoliaTokenHolderBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) + const adapterBepoliaBalance = await bepoliaToken.balanceOf(adapterAddr) + log('bepoliaTokenHolder and adapter balances: ', bepoliaTokenHolderBalance, adapterBepoliaBalance) + // We need to have exactly 1 Bepolia token in the adapter + assert.equals(adapterBepoliaBalance, 1) const result = await depositAdapter.test() - console.log('result', result) - await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot) - - const bal1 = await bepoliaToken.balanceOf(bepoliaTokenHolder) - const bal2 = await bepoliaToken.balanceOf(depositCaller) - console.log('balances', bal1, bal2) + assert.equals(result, 'Sepolia deposit contract token') + + const depositRootBefore = await depositAdapter.get_deposit_root() + log('depositRoot', depositRootBefore) + const depositCountBefore = await depositAdapter.get_deposit_count() + log('depositCount', depositCountBefore) + + await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot, { + from: owner.address, + value: ethers.utils.parseEther('32.0'), + }) + const depositRootAfter = await depositAdapter.get_deposit_root() + log('depositRoot After', depositRootAfter) + const depositCountAfter = await depositAdapter.get_deposit_count() + log('depositCount After', depositCountAfter) + assert.notEqual(depositRootBefore, depositRootAfter) + assert.equals(BigInt(depositCountBefore) + BigInt('0x0100000000000000'), BigInt(depositCountAfter)) + + const ethAfterDeposit = await ethers.provider.getBalance(adapterAddr) + log('ethAfterDeposit', ethAfterDeposit.toString()) + assert.equals(ethAfterDeposit, '32000000000000000000') + + await depositAdapter.drain() + const balanceEthAfterDrain = await ethers.provider.getBalance(adapterAddr) + log('balanceEthAfterDrain', balanceEthAfterDrain.toString()) + assert.equals(balanceEthAfterDrain, 0) + + const adapterBepoliaBalanceAfter = await bepoliaToken.balanceOf(adapterAddr) + assert.equals(adapterBepoliaBalanceAfter, 0) }) }) }) From 90f4b0370747ba5b5b69295c20b5128fa61cb003 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 16:45:36 +0100 Subject: [PATCH 17/41] Add owner logging --- test/0.8.9/sepolia-deposit-adapter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 57ce9965f..57f19a75f 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -77,7 +77,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { await bepoliaToken.connect(impersonatedSigner).transfer(adapterAddr, 1) const [owner] = await ethers.getSigners() - // log('owner', owner.address); + log('owner', owner.address) // await owner.sendTransaction({ // to: adapterAddr, // value: ethers.utils.parseEther("10.0"), // Sends exactly 1.0 ether From f13cb41c62928cc5ff663213197dd5f331b7fff7 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 16:59:31 +0100 Subject: [PATCH 18/41] Add auto drain to deposit() and tests around --- contracts/0.8.9/SepoliaDepositAdapter.sol | 11 ++++----- test/0.8.9/sepolia-deposit-adapter.test.js | 26 ++++++++++++++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 3eaff9b6f..245c20f0b 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -48,16 +48,11 @@ contract SepoliaDepositAdapter is Ownable { } receive() external payable { - uint ownTokens = address(this).balance; console.log( - "Receive %s tokens from %s (own %d)", + "Receive %s from %s", msg.value, - msg.sender, - ownTokens + msg.sender ); - // address payable sendTo = payable(owner()); - // address payable sendTo = payable(address(0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5)); - // sendTo.transfer(ownTokens); } function drain() external onlyOwner { @@ -83,5 +78,7 @@ contract SepoliaDepositAdapter is Ownable { msg.sender ); originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root); + address payable owner = payable(owner()); + owner.transfer(msg.value); } } diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 57f19a75f..8afa57f70 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -110,15 +110,33 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { const ethAfterDeposit = await ethers.provider.getBalance(adapterAddr) log('ethAfterDeposit', ethAfterDeposit.toString()) - assert.equals(ethAfterDeposit, '32000000000000000000') + assert.equals(ethAfterDeposit, 0) + + const adapterBepoliaBalanceAfter = await bepoliaToken.balanceOf(adapterAddr) + assert.equals(adapterBepoliaBalanceAfter, 0) + }) + + it(`call drain on Adapter`, async () => { + const adapterAddr = depositAdapter.address + + const balance0ETH = await ethers.provider.getBalance(adapterAddr) + assert.equals(balance0ETH, 0) + + const [owner] = await ethers.getSigners() + log('owner', owner.address) + await owner.sendTransaction({ + to: adapterAddr, + value: ethers.utils.parseEther('10.0'), // Sends exactly 1.0 ether + }) + + const ethAfterDeposit = await ethers.provider.getBalance(adapterAddr) + log('ethAfterDeposit', ethAfterDeposit.toString()) + assert.equals(ethAfterDeposit, ethers.utils.parseEther('10.0')) await depositAdapter.drain() const balanceEthAfterDrain = await ethers.provider.getBalance(adapterAddr) log('balanceEthAfterDrain', balanceEthAfterDrain.toString()) assert.equals(balanceEthAfterDrain, 0) - - const adapterBepoliaBalanceAfter = await bepoliaToken.balanceOf(adapterAddr) - assert.equals(adapterBepoliaBalanceAfter, 0) }) }) }) From 3ff2df1e31f1c8191bbdc63e427aee527f9c369a Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 17:08:37 +0100 Subject: [PATCH 19/41] Fix contract warning --- contracts/0.8.9/SepoliaDepositAdapter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 245c20f0b..63043549f 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -63,7 +63,8 @@ contract SepoliaDepositAdapter is Ownable { function drainBepolia() external onlyOwner { uint bepoliaOwnTokens = originalContract.balanceOf(address(this)); - originalContract.transfer(owner(), bepoliaOwnTokens); + bool success = originalContract.transfer(owner(), bepoliaOwnTokens); + require(success, "Transfer failed"); } function deposit( From f1224224113fe015b560379a65bb2220f89c309d Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 17:09:58 +0100 Subject: [PATCH 20/41] Fix deployment test --- test/0.8.9/sepolia-deployment.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/0.8.9/sepolia-deployment.test.js b/test/0.8.9/sepolia-deployment.test.js index 4a35ac8d8..aa0138f4d 100644 --- a/test/0.8.9/sepolia-deployment.test.js +++ b/test/0.8.9/sepolia-deployment.test.js @@ -1,17 +1,16 @@ const { artifacts, contract, ethers } = require('hardhat') +const { assert } = require('../helpers/assert') const { EvmSnapshot } = require('../helpers/blockchain') const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') -contract('SepoliaDepositAdapter', ([deployer]) => { +contract('SepoliaDepositAdapter deployment', ([deployer]) => { let depositAdapter let snapshot before('deploy lido with dao', async () => { depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) - const dna = await depositAdapter.TEST_VALUE() - console.log(dna) snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() @@ -22,6 +21,9 @@ contract('SepoliaDepositAdapter', ([deployer]) => { }) describe('SepoliaDepositAdapter Logic', () => { - it(`state after deployment`, async () => {}) + it(`state after deployment`, async () => { + const depositAdapterVersion = await depositAdapter.VERSION() + assert.equals(depositAdapterVersion, 2) + }) }) }) From 3180ee5a705c3cd755d8941087daa98126a0b23d Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 14 Feb 2024 17:12:14 +0100 Subject: [PATCH 21/41] Remove cruft --- test/0.8.9/sepolia-deposit-adapter.test.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 8afa57f70..9f6e766a2 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -78,10 +78,6 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { const [owner] = await ethers.getSigners() log('owner', owner.address) - // await owner.sendTransaction({ - // to: adapterAddr, - // value: ethers.utils.parseEther("10.0"), // Sends exactly 1.0 ether - // }); const bepoliaTokenHolderBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) const adapterBepoliaBalance = await bepoliaToken.balanceOf(adapterAddr) @@ -126,7 +122,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { log('owner', owner.address) await owner.sendTransaction({ to: adapterAddr, - value: ethers.utils.parseEther('10.0'), // Sends exactly 1.0 ether + value: ethers.utils.parseEther('10.0'), }) const ethAfterDeposit = await ethers.provider.getBalance(adapterAddr) From b5193c15619bf506a6aa5716bfca6d62de2605dd Mon Sep 17 00:00:00 2001 From: Victor P <157478184+vp42ldo@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:03:07 +0300 Subject: [PATCH 22/41] More readable name Co-authored-by: Aleksei Potapkin --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 63043549f..42cd2121e 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -43,7 +43,7 @@ contract SepoliaDepositAdapter is Ownable { return originalContract.get_deposit_count(); } - function test() external view returns (string memory) { + function name() external view returns (string memory) { return originalContract.name(); } From d14eb54d650231cdb3ccbd9d773d78e00a3dbffb Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 12:06:48 +0100 Subject: [PATCH 23/41] Fix naming --- contracts/0.8.9/SepoliaDepositAdapter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 42cd2121e..ff2020de7 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -56,9 +56,9 @@ contract SepoliaDepositAdapter is Ownable { } function drain() external onlyOwner { - uint ownTokens = address(this).balance; + uint balance = address(this).balance; address payable _owner = payable(owner()); - _owner.transfer(ownTokens); + _owner.transfer(balance); } function drainBepolia() external onlyOwner { From 97db353bebe1d3bbe81c1801313caf2ffe4b6090 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 15:55:46 +0100 Subject: [PATCH 24/41] Fix test --- test/0.8.9/sepolia-deposit-adapter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 9f6e766a2..835d8ea91 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -85,7 +85,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { // We need to have exactly 1 Bepolia token in the adapter assert.equals(adapterBepoliaBalance, 1) - const result = await depositAdapter.test() + const result = await depositAdapter.name() assert.equals(result, 'Sepolia deposit contract token') const depositRootBefore = await depositAdapter.get_deposit_root() From b752379a5f13ad4bfda7462f3c0114652b0f6217 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 15:56:10 +0100 Subject: [PATCH 25/41] Skip test for local node testing --- test/0.8.9/sepolia-deployment.test.js | 6 +++++- test/0.8.9/sepolia-deposit-adapter.test.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/0.8.9/sepolia-deployment.test.js b/test/0.8.9/sepolia-deployment.test.js index aa0138f4d..5da321e34 100644 --- a/test/0.8.9/sepolia-deployment.test.js +++ b/test/0.8.9/sepolia-deployment.test.js @@ -9,7 +9,11 @@ contract('SepoliaDepositAdapter deployment', ([deployer]) => { let depositAdapter let snapshot - before('deploy lido with dao', async () => { + before('deploy lido with dao', async function () { + if (!process.env.HARDHAT_FORKING_URL) { + return this.skip() + } + depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) snapshot = new EvmSnapshot(ethers.provider) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 835d8ea91..7c61e67df 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -14,7 +14,11 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { // const log = console.log const log = () => {} - before('deploy lido with dao', async () => { + before('deploy lido with dao', async function () { + if (!process.env.HARDHAT_FORKING_URL) { + return this.skip() + } + // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) depositAdapter = await ethers.deployContract('SepoliaDepositAdapter', [sepoliaDepositContract]) log('depositAdapter address', depositAdapter.address) From 80d018d5eb03c6ccb0e0308e928aad17de0a3684 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 16:18:36 +0100 Subject: [PATCH 26/41] Provide address for drain --- contracts/0.8.9/SepoliaDepositAdapter.sol | 5 ++--- test/0.8.9/sepolia-deposit-adapter.test.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index ff2020de7..0ed0b3fd8 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -55,10 +55,9 @@ contract SepoliaDepositAdapter is Ownable { ); } - function drain() external onlyOwner { + function drain(address payable destination) external onlyOwner { uint balance = address(this).balance; - address payable _owner = payable(owner()); - _owner.transfer(balance); + destination.transfer(balance); } function drainBepolia() external onlyOwner { diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 7c61e67df..80d3b7962 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -133,7 +133,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { log('ethAfterDeposit', ethAfterDeposit.toString()) assert.equals(ethAfterDeposit, ethers.utils.parseEther('10.0')) - await depositAdapter.drain() + await depositAdapter.drain(owner.address) const balanceEthAfterDrain = await ethers.provider.getBalance(adapterAddr) log('balanceEthAfterDrain', balanceEthAfterDrain.toString()) assert.equals(balanceEthAfterDrain, 0) From 04d4267c11bf6935bdd7bf5231fe49d328a9c856 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 16:19:49 +0100 Subject: [PATCH 27/41] Comment on how run Sepolia-specific tests --- test/0.8.9/sepolia-deposit-adapter.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 80d3b7962..5f556642f 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -3,6 +3,8 @@ const { assert } = require('../helpers/assert') const { EvmSnapshot } = require('../helpers/blockchain') +// To run Sepolia Deposit Adapter tests: +// HARDHAT_FORKING_URL= npx hardhat test --grep "SepoliaDepositAdapter" contract('SepoliaDepositAdapter impl', ([deployer]) => { let depositAdapter let snapshot From fb7338594a9d83fad3daa4171d489d3b3fb86020 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 17:39:38 +0100 Subject: [PATCH 28/41] Add chain id --- hardhat.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hardhat.config.js b/hardhat.config.js index 72db0598e..7ab85921c 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -120,6 +120,9 @@ const getNetConfig = (networkName, ethAccountName) => { if (networkName === 'hardhat' && process.env.HARDHAT_FORKING_URL) { netConfig.forking = { url: process.env.HARDHAT_FORKING_URL } } + if (networkName === 'hardhat' && process.env.HARDHAT_CHAIN_ID) { + netConfig.chainId = +process.env.HARDHAT_CHAIN_ID + } return netConfig ? { [networkName]: netConfig } : {} } From a5793ec8e2d005aa88f1fea61cc452095642843f Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 17:41:14 +0100 Subject: [PATCH 29/41] Fix contract Improve naming, remove logging, replace transfer() with call(), remove version, add error on call failure --- contracts/0.8.9/SepoliaDepositAdapter.sol | 34 ++++++++++------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 0ed0b3fd8..cbb1bf820 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -7,8 +7,6 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/access/Ownable.sol"; -import "hardhat/console.sol"; - // Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol interface ISepoliaDepositContract is IERC20 { @@ -28,9 +26,10 @@ interface ISepoliaDepositContract is IERC20 { contract SepoliaDepositAdapter is Ownable { - uint public constant VERSION = 2; ISepoliaDepositContract public immutable originalContract; + error TransferFailed(); + constructor(address _deposit_contract) { originalContract = ISepoliaDepositContract(_deposit_contract); } @@ -48,22 +47,22 @@ contract SepoliaDepositAdapter is Ownable { } receive() external payable { - console.log( - "Receive %s from %s", - msg.value, - msg.sender - ); } - function drain(address payable destination) external onlyOwner { + function recoverEth() external onlyOwner { uint balance = address(this).balance; - destination.transfer(balance); + (bool success,) = owner().call{value: balance}(""); + if (!success) { + revert TransferFailed(); + } } - function drainBepolia() external onlyOwner { + function recoverBepolia() external onlyOwner { uint bepoliaOwnTokens = originalContract.balanceOf(address(this)); bool success = originalContract.transfer(owner(), bepoliaOwnTokens); - require(success, "Transfer failed"); + if (!success) { + revert TransferFailed(); + } } function deposit( @@ -72,13 +71,10 @@ contract SepoliaDepositAdapter is Ownable { bytes calldata signature, bytes32 deposit_data_root ) external payable { - console.log( - "Deposit with %s ETH from %s", - msg.value, - msg.sender - ); originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root); - address payable owner = payable(owner()); - owner.transfer(msg.value); + (bool success,) = owner().call{value: msg.value}(""); + if (!success) { + revert TransferFailed(); + } } } From 15320beae0cb6f1b6b0ea4774d68e24d9db5c6e1 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 15 Feb 2024 17:41:37 +0100 Subject: [PATCH 30/41] Combine tests and fix them after renamings --- test/0.8.9/sepolia-deployment.test.js | 33 ---------------------- test/0.8.9/sepolia-deposit-adapter.test.js | 32 ++++++++++----------- 2 files changed, 15 insertions(+), 50 deletions(-) delete mode 100644 test/0.8.9/sepolia-deployment.test.js diff --git a/test/0.8.9/sepolia-deployment.test.js b/test/0.8.9/sepolia-deployment.test.js deleted file mode 100644 index 5da321e34..000000000 --- a/test/0.8.9/sepolia-deployment.test.js +++ /dev/null @@ -1,33 +0,0 @@ -const { artifacts, contract, ethers } = require('hardhat') -const { assert } = require('../helpers/assert') - -const { EvmSnapshot } = require('../helpers/blockchain') - -const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') - -contract('SepoliaDepositAdapter deployment', ([deployer]) => { - let depositAdapter - let snapshot - - before('deploy lido with dao', async function () { - if (!process.env.HARDHAT_FORKING_URL) { - return this.skip() - } - - depositAdapter = await SepoliaDepositAdapter.new(deployer, { from: deployer }) - - snapshot = new EvmSnapshot(ethers.provider) - await snapshot.make() - }) - - afterEach(async () => { - await snapshot.rollback() - }) - - describe('SepoliaDepositAdapter Logic', () => { - it(`state after deployment`, async () => { - const depositAdapterVersion = await depositAdapter.VERSION() - assert.equals(depositAdapterVersion, 2) - }) - }) -}) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 5f556642f..040370f86 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -4,8 +4,8 @@ const { assert } = require('../helpers/assert') const { EvmSnapshot } = require('../helpers/blockchain') // To run Sepolia Deposit Adapter tests: -// HARDHAT_FORKING_URL= npx hardhat test --grep "SepoliaDepositAdapter" -contract('SepoliaDepositAdapter impl', ([deployer]) => { +// HARDHAT_FORKING_URL= HARDHAT_CHAIN_ID=11155111 npx hardhat test --grep "SepoliaDepositAdapter" +contract('SepoliaDepositAdapter', ([deployer]) => { let depositAdapter let snapshot let bepoliaToken @@ -17,7 +17,8 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { const log = () => {} before('deploy lido with dao', async function () { - if (!process.env.HARDHAT_FORKING_URL) { + const { chainId } = await ethers.provider.getNetwork() + if (chainId !== 11155111) { return this.skip() } @@ -25,11 +26,11 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { depositAdapter = await ethers.deployContract('SepoliaDepositAdapter', [sepoliaDepositContract]) log('depositAdapter address', depositAdapter.address) - const depositAdapterVersion = await depositAdapter.VERSION() - assert.equals(depositAdapterVersion, 2) - bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContract) + const result = await depositAdapter.name() + assert.equals(result, 'Sepolia deposit contract token') + snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() }) @@ -39,7 +40,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { }) describe('SepoliaDepositAdapter Logic', () => { - it(`transfer Bepolia tokens`, async () => { + it(`recover Bepolia tokens`, async () => { const adapterAddr = depositAdapter.address const BEPOLIA_TO_TRANSFER = 2 const bepoliaHolderInitialBalance = await bepoliaToken.balanceOf(bepoliaTokenHolder) @@ -54,8 +55,8 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { assert.equals(bepoliaHolderEndBalance, bepoliaHolderInitialBalance - BEPOLIA_TO_TRANSFER) log('bepoliaHolderEndBalance', bepoliaHolderEndBalance) - // Drain Bepolia tokens - await depositAdapter.drainBepolia() + // Recover Bepolia tokens + await depositAdapter.recoverBepolia() const bepoliaTokensOnAdapter = await bepoliaToken.balanceOf(adapterAddr) assert.equals(bepoliaTokensOnAdapter, 0) @@ -91,9 +92,6 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { // We need to have exactly 1 Bepolia token in the adapter assert.equals(adapterBepoliaBalance, 1) - const result = await depositAdapter.name() - assert.equals(result, 'Sepolia deposit contract token') - const depositRootBefore = await depositAdapter.get_deposit_root() log('depositRoot', depositRootBefore) const depositCountBefore = await depositAdapter.get_deposit_count() @@ -118,7 +116,7 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { assert.equals(adapterBepoliaBalanceAfter, 0) }) - it(`call drain on Adapter`, async () => { + it(`recover ETH`, async () => { const adapterAddr = depositAdapter.address const balance0ETH = await ethers.provider.getBalance(adapterAddr) @@ -135,10 +133,10 @@ contract('SepoliaDepositAdapter impl', ([deployer]) => { log('ethAfterDeposit', ethAfterDeposit.toString()) assert.equals(ethAfterDeposit, ethers.utils.parseEther('10.0')) - await depositAdapter.drain(owner.address) - const balanceEthAfterDrain = await ethers.provider.getBalance(adapterAddr) - log('balanceEthAfterDrain', balanceEthAfterDrain.toString()) - assert.equals(balanceEthAfterDrain, 0) + await depositAdapter.recoverEth() + const balanceEthAfterRecover = await ethers.provider.getBalance(adapterAddr) + log('balanceEthAfterRecover', balanceEthAfterRecover.toString()) + assert.equals(balanceEthAfterRecover, 0) }) }) }) From b334ea4a5931574ab3cc4f98919ff3f9d56e9d7e Mon Sep 17 00:00:00 2001 From: Victor P <157478184+vp42ldo@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:22:43 +0300 Subject: [PATCH 31/41] Update contracts/0.8.9/SepoliaDepositAdapter.sol Co-authored-by: Eugene Mamin --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index cbb1bf820..19a6cd680 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -50,7 +50,7 @@ contract SepoliaDepositAdapter is Ownable { } function recoverEth() external onlyOwner { - uint balance = address(this).balance; + uint256 balance = address(this).balance; (bool success,) = owner().call{value: balance}(""); if (!success) { revert TransferFailed(); From 401b8869bf56864d8869f70796c27fc086ea767f Mon Sep 17 00:00:00 2001 From: Victor P <157478184+vp42ldo@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:30:18 +0300 Subject: [PATCH 32/41] Update contracts/0.8.9/SepoliaDepositAdapter.sol Co-authored-by: Eugene Mamin --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 19a6cd680..c54453f43 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -58,7 +58,7 @@ contract SepoliaDepositAdapter is Ownable { } function recoverBepolia() external onlyOwner { - uint bepoliaOwnTokens = originalContract.balanceOf(address(this)); + uint256 bepoliaOwnTokens = originalContract.balanceOf(address(this)); bool success = originalContract.transfer(owner(), bepoliaOwnTokens); if (!success) { revert TransferFailed(); From 312d7082cf76212484abb7d2726072c7817e7c9b Mon Sep 17 00:00:00 2001 From: Victor P <157478184+vp42ldo@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:30:34 +0300 Subject: [PATCH 33/41] Update test/0.8.9/sepolia-deposit-adapter.test.js Co-authored-by: Eugene Mamin --- test/0.8.9/sepolia-deposit-adapter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 040370f86..bf42e902b 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -28,7 +28,7 @@ contract('SepoliaDepositAdapter', ([deployer]) => { bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContract) - const result = await depositAdapter.name() + const name = await depositAdapter.name() assert.equals(result, 'Sepolia deposit contract token') snapshot = new EvmSnapshot(ethers.provider) From e82e9b309a73b28f5530de7123693f09a47097ff Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 16:16:27 +0100 Subject: [PATCH 34/41] Extract interfaces and improve adapter contract --- contracts/0.8.9/SepoliaDepositAdapter.sol | 44 +++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index c54453f43..25a1e8958 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -7,9 +7,7 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/access/Ownable.sol"; -// Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol -interface ISepoliaDepositContract is IERC20 { - +interface IDepositContract { function deposit( bytes calldata pubkey, bytes calldata withdrawal_credentials, @@ -20,48 +18,58 @@ interface ISepoliaDepositContract is IERC20 { function get_deposit_root() external view returns (bytes32); function get_deposit_count() external view returns (bytes memory); - - function name() external view returns (string memory); } -contract SepoliaDepositAdapter is Ownable { +// Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol +interface ISepoliaDepositContract is IDepositContract, IERC20 { } - ISepoliaDepositContract public immutable originalContract; +// Sepolia testnet deposit contract have a different interface than the mainnet deposit contract. +// This adapter is used to make the mainnet deposit contract compatible with the testnet deposit contract. +// For further information see Sepolia deposit contract variant source code link above. +contract SepoliaDepositAdapter is IDepositContract, Ownable { + + event EthReceived(address sender, uint256 amount); + + event EthRecovered(uint256 amount); - error TransferFailed(); + error EthRecoverFailed(); + + error BepoliaRecoverFailed(); + + error DepositFailed(); + + ISepoliaDepositContract public immutable originalContract; constructor(address _deposit_contract) { originalContract = ISepoliaDepositContract(_deposit_contract); } - function get_deposit_root() external view returns (bytes32) { + function get_deposit_root() override external view returns (bytes32) { return originalContract.get_deposit_root(); } - function get_deposit_count() external view returns (bytes memory) { + function get_deposit_count() override external view returns (bytes memory) { return originalContract.get_deposit_count(); } - function name() external view returns (string memory) { - return originalContract.name(); - } - receive() external payable { + emit EthReceived(msg.sender, msg.value); } function recoverEth() external onlyOwner { uint256 balance = address(this).balance; (bool success,) = owner().call{value: balance}(""); if (!success) { - revert TransferFailed(); + revert EthRecoverFailed(); } + emit EthRecovered(balance); } function recoverBepolia() external onlyOwner { uint256 bepoliaOwnTokens = originalContract.balanceOf(address(this)); bool success = originalContract.transfer(owner(), bepoliaOwnTokens); if (!success) { - revert TransferFailed(); + revert BepoliaRecoverFailed(); } } @@ -70,11 +78,11 @@ contract SepoliaDepositAdapter is Ownable { bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root - ) external payable { + ) override external payable { originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root); (bool success,) = owner().call{value: msg.value}(""); if (!success) { - revert TransferFailed(); + revert DepositFailed(); } } } From c9169790fd1b20eb6ad4d3cbd881856d85c0767b Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 16:21:06 +0100 Subject: [PATCH 35/41] Fix tests for contract changes --- test/0.8.9/sepolia-deposit-adapter.test.js | 29 +++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index bf42e902b..e0fb654d8 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -1,15 +1,17 @@ -const { contract, ethers } = require('hardhat') +const { contract, artifacts, ethers } = require('hardhat') const { assert } = require('../helpers/assert') +const { ETH } = require('../helpers/utils') const { EvmSnapshot } = require('../helpers/blockchain') +const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') + // To run Sepolia Deposit Adapter tests: // HARDHAT_FORKING_URL= HARDHAT_CHAIN_ID=11155111 npx hardhat test --grep "SepoliaDepositAdapter" contract('SepoliaDepositAdapter', ([deployer]) => { let depositAdapter let snapshot let bepoliaToken - // const sepoliaDepositAdapterContract = '0x899e45316FaA439200b36c7d7733192530e3DfC0' const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' const bepoliaTokenHolder = EOAddress @@ -22,14 +24,13 @@ contract('SepoliaDepositAdapter', ([deployer]) => { return this.skip() } - // depositAdapter = await SepoliaDepositAdapter.at(sepoliaDepositAdapterContract) - depositAdapter = await ethers.deployContract('SepoliaDepositAdapter', [sepoliaDepositContract]) + depositAdapter = await SepoliaDepositAdapter.new(sepoliaDepositContract) log('depositAdapter address', depositAdapter.address) bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContract) - const name = await depositAdapter.name() - assert.equals(result, 'Sepolia deposit contract token') + const code = await ethers.provider.getCode(depositAdapter.address) + assert.notEqual(code, '0x') snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() @@ -79,7 +80,6 @@ contract('SepoliaDepositAdapter', ([deployer]) => { assert.equals(balance0ETH, 0) const impersonatedSigner = await ethers.getImpersonatedSigner(bepoliaTokenHolder) - await depositAdapter.connect(impersonatedSigner) // Transfer 1 Bepolia token to depositCaller await bepoliaToken.connect(impersonatedSigner).transfer(adapterAddr, 1) @@ -97,10 +97,12 @@ contract('SepoliaDepositAdapter', ([deployer]) => { const depositCountBefore = await depositAdapter.get_deposit_count() log('depositCount', depositCountBefore) - await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot, { + const receipt = await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot, { from: owner.address, - value: ethers.utils.parseEther('32.0'), + value: ETH(32), }) + assert.emits(receipt, 'EthReceived', { sender: sepoliaDepositContract, amount: ETH(32) }) + const depositRootAfter = await depositAdapter.get_deposit_root() log('depositRoot After', depositRootAfter) const depositCountAfter = await depositAdapter.get_deposit_count() @@ -117,6 +119,7 @@ contract('SepoliaDepositAdapter', ([deployer]) => { }) it(`recover ETH`, async () => { + const ETH_TO_TRANSFER = ETH(10) const adapterAddr = depositAdapter.address const balance0ETH = await ethers.provider.getBalance(adapterAddr) @@ -126,14 +129,16 @@ contract('SepoliaDepositAdapter', ([deployer]) => { log('owner', owner.address) await owner.sendTransaction({ to: adapterAddr, - value: ethers.utils.parseEther('10.0'), + value: ETH_TO_TRANSFER, }) const ethAfterDeposit = await ethers.provider.getBalance(adapterAddr) log('ethAfterDeposit', ethAfterDeposit.toString()) - assert.equals(ethAfterDeposit, ethers.utils.parseEther('10.0')) + assert.equals(ethAfterDeposit, ETH_TO_TRANSFER) + + const receipt = await depositAdapter.recoverEth() + assert.emits(receipt, 'EthRecovered', { amount: ETH_TO_TRANSFER }) - await depositAdapter.recoverEth() const balanceEthAfterRecover = await ethers.provider.getBalance(adapterAddr) log('balanceEthAfterRecover', balanceEthAfterRecover.toString()) assert.equals(balanceEthAfterRecover, 0) From e3a089ed8a4fa3fd8963dc63d6810ada59719dd0 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 16:21:42 +0100 Subject: [PATCH 36/41] Suppress warnings --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 25a1e8958..a4f251f62 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -58,6 +58,7 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable { function recoverEth() external onlyOwner { uint256 balance = address(this).balance; + // solhint-disable-next-line avoid-low-level-calls (bool success,) = owner().call{value: balance}(""); if (!success) { revert EthRecoverFailed(); @@ -80,6 +81,7 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable { bytes32 deposit_data_root ) override external payable { originalContract.deposit{value: msg.value}(pubkey, withdrawal_credentials, signature, deposit_data_root); + // solhint-disable-next-line avoid-low-level-calls (bool success,) = owner().call{value: msg.value}(""); if (!success) { revert DepositFailed(); From c9d0887195c13cc5a75b726ac329eeb865ae1174 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 16:28:21 +0100 Subject: [PATCH 37/41] Details on while Adapter is required --- contracts/0.8.9/SepoliaDepositAdapter.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index a4f251f62..53b42aee8 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -24,6 +24,9 @@ interface IDepositContract { interface ISepoliaDepositContract is IDepositContract, IERC20 { } // Sepolia testnet deposit contract have a different interface than the mainnet deposit contract. +// The differences are: +// 1. Sepolia contract require specific Bepolia token to be used for depositing. It burns this token after depositing. +// 2. It returns the ETH to the sender after depositing. // This adapter is used to make the mainnet deposit contract compatible with the testnet deposit contract. // For further information see Sepolia deposit contract variant source code link above. contract SepoliaDepositAdapter is IDepositContract, Ownable { From a3e2982c7623f55000656cf9a78da4a67d405447 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 17:41:03 +0100 Subject: [PATCH 38/41] Add DepositEvent and checks for it --- contracts/0.8.9/SepoliaDepositAdapter.sol | 8 ++++++++ test/0.8.9/sepolia-deposit-adapter.test.js | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 53b42aee8..5e7f785bd 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -8,6 +8,14 @@ import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/access/Ownable.sol"; interface IDepositContract { + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + function deposit( bytes calldata pubkey, bytes calldata withdrawal_credentials, diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index e0fb654d8..869974698 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -5,6 +5,7 @@ const { ETH } = require('../helpers/utils') const { EvmSnapshot } = require('../helpers/blockchain') const SepoliaDepositAdapter = artifacts.require('SepoliaDepositAdapter') +const SepoliaDepositContract = artifacts.require('ISepoliaDepositContract') // To run Sepolia Deposit Adapter tests: // HARDHAT_FORKING_URL= HARDHAT_CHAIN_ID=11155111 npx hardhat test --grep "SepoliaDepositAdapter" @@ -12,7 +13,7 @@ contract('SepoliaDepositAdapter', ([deployer]) => { let depositAdapter let snapshot let bepoliaToken - const sepoliaDepositContract = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' + const sepoliaDepositContractAddress = '0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D' const EOAddress = '0x6885E36BFcb68CB383DfE90023a462C03BCB2AE5' const bepoliaTokenHolder = EOAddress // const log = console.log @@ -24,10 +25,10 @@ contract('SepoliaDepositAdapter', ([deployer]) => { return this.skip() } - depositAdapter = await SepoliaDepositAdapter.new(sepoliaDepositContract) + depositAdapter = await SepoliaDepositAdapter.new(sepoliaDepositContractAddress) log('depositAdapter address', depositAdapter.address) - bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContract) + bepoliaToken = await ethers.getContractAt('ISepoliaDepositContract', sepoliaDepositContractAddress) const code = await ethers.provider.getCode(depositAdapter.address) assert.notEqual(code, '0x') @@ -97,11 +98,20 @@ contract('SepoliaDepositAdapter', ([deployer]) => { const depositCountBefore = await depositAdapter.get_deposit_count() log('depositCount', depositCountBefore) + const sepoliaDepositContract = await SepoliaDepositContract.at(sepoliaDepositContractAddress) + const receipt = await depositAdapter.deposit(key, withdrawalCredentials, sig, dataRoot, { from: owner.address, value: ETH(32), }) - assert.emits(receipt, 'EthReceived', { sender: sepoliaDepositContract, amount: ETH(32) }) + assert.emits(receipt, 'EthReceived', { sender: sepoliaDepositContractAddress, amount: ETH(32) }) + const depositEvents = await sepoliaDepositContract.getPastEvents('DepositEvent') + assert.equals(depositEvents.length, 1) + log('depositEvents', depositEvents, ETH(32)) + + assert.equals(depositEvents[0].args.pubkey, key) + assert.equals(depositEvents[0].args.withdrawal_credentials, withdrawalCredentials) + assert.equals(depositEvents[0].args.signature, sig) const depositRootAfter = await depositAdapter.get_deposit_root() log('depositRoot After', depositRootAfter) From a802e4332252e0e148469177e4016fd643084121 Mon Sep 17 00:00:00 2001 From: VP Date: Tue, 20 Feb 2024 17:45:15 +0100 Subject: [PATCH 39/41] Fix comment --- contracts/0.8.9/SepoliaDepositAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index 5e7f785bd..a6ee6d8d2 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -31,7 +31,7 @@ interface IDepositContract { // Sepolia deposit contract variant of the source code https://github.com/protolambda/testnet-dep-contract/blob/master/deposit_contract.sol interface ISepoliaDepositContract is IDepositContract, IERC20 { } -// Sepolia testnet deposit contract have a different interface than the mainnet deposit contract. +// Sepolia testnet deposit contract have a bit different logic than the mainnet deposit contract. // The differences are: // 1. Sepolia contract require specific Bepolia token to be used for depositing. It burns this token after depositing. // 2. It returns the ETH to the sender after depositing. From 7536ba8c8b9210c470797962898c07de7e2c9d31 Mon Sep 17 00:00:00 2001 From: Eugene M Date: Tue, 20 Feb 2024 21:37:28 +0300 Subject: [PATCH 40/41] fix: update storage layout action --- .github/workflows/storage-layout-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/storage-layout-check.yml b/.github/workflows/storage-layout-check.yml index 597aaf309..a4904df88 100644 --- a/.github/workflows/storage-layout-check.yml +++ b/.github/workflows/storage-layout-check.yml @@ -32,4 +32,4 @@ jobs: mode: check src-folder: ./contracts ignore-folders: '{test_helpers,template,mocks}' - ignore-contracts: 'WithdrawalsManagerProxy|WithdrawalsManagerStub|ERC1967Proxy' + ignore-contracts: 'WithdrawalsManagerProxy|WithdrawalsManagerStub|ERC1967Proxy|SepoliaDepositAdapter' From 248065153dd62e5083ce7b5b30e9a100d5104689 Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 21 Feb 2024 09:57:54 +0100 Subject: [PATCH 41/41] Emit event on Bepolia recover --- contracts/0.8.9/SepoliaDepositAdapter.sol | 3 +++ test/0.8.9/sepolia-deposit-adapter.test.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/SepoliaDepositAdapter.sol b/contracts/0.8.9/SepoliaDepositAdapter.sol index a6ee6d8d2..48e5fa863 100644 --- a/contracts/0.8.9/SepoliaDepositAdapter.sol +++ b/contracts/0.8.9/SepoliaDepositAdapter.sol @@ -43,6 +43,8 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable { event EthRecovered(uint256 amount); + event BepoliaRecovered(uint256 amount); + error EthRecoverFailed(); error BepoliaRecoverFailed(); @@ -83,6 +85,7 @@ contract SepoliaDepositAdapter is IDepositContract, Ownable { if (!success) { revert BepoliaRecoverFailed(); } + emit BepoliaRecovered(bepoliaOwnTokens); } function deposit( diff --git a/test/0.8.9/sepolia-deposit-adapter.test.js b/test/0.8.9/sepolia-deposit-adapter.test.js index 869974698..8c23890f1 100644 --- a/test/0.8.9/sepolia-deposit-adapter.test.js +++ b/test/0.8.9/sepolia-deposit-adapter.test.js @@ -58,7 +58,8 @@ contract('SepoliaDepositAdapter', ([deployer]) => { log('bepoliaHolderEndBalance', bepoliaHolderEndBalance) // Recover Bepolia tokens - await depositAdapter.recoverBepolia() + const receipt = await depositAdapter.recoverBepolia() + assert.emits(receipt, 'BepoliaRecovered', { amount: BEPOLIA_TO_TRANSFER }) const bepoliaTokensOnAdapter = await bepoliaToken.balanceOf(adapterAddr) assert.equals(bepoliaTokensOnAdapter, 0)