Transaction Hash:
Block:
17035779 at Apr-13-2023 02:02:47 AM +UTC
Transaction Fee:
0.001970557972128959 ETH
$4.78
Gas Used:
73,613 Gas / 26.769157243 Gwei
Emitted Events:
169 |
DepositContract.DepositEvent( pubkey=0xAD1FE46AEE30697B3DCC81E129093370E33D329EC8E6B6D51BE757B8C1357BEE58E0147C7ADF31CE9E8719A501BC81E9, withdrawal_credentials=0x010000000000000000000000662F3F39FEDB7C57C2FA855E6BD234BE52595728, amount=0x0040597307000000, signature=0x9543FF9777529607ED590DF1D069AD018F081B97CCD846D1656B8047FBF0520A424C959B0A38BB75ED8D0D739BC415DB0AE597542772AA532E4CE47F8C2D64EF3FA3B20EF85D4A0BD2656734FF1770265AF047419D5EB1BE40147BD445221518, index=0x55E2080000000000 )
|
170 |
AbyssEth2Depositor.DepositEvent( from=[Sender] 0x662f3f39fedb7c57c2fa855e6bd234be52595728, nodesAmount=1 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x00000000...03d7705Fa | (Beacon Deposit Contract) | 18,168,103.168232416675747075 Eth | 18,168,135.168232416675747075 Eth | 32 | |
0x2990555A...763727088
Miner
| (Fee Recipient: 0x2990...088) | 74.994237523907581434 Eth | 74.994342642184685489 Eth | 0.000105118277104055 | |
0x662f3F39...E52595728 |
89.577552727122487558 Eth
Nonce: 117
|
57.575582169150358599 Eth
Nonce: 118
| 32.001970557972128959 |
Execution Trace
ETH 32
AbyssEth2Depositor.deposit( )
ETH 32
DepositContract.deposit( pubkey=0xAD1FE46AEE30697B3DCC81E129093370E33D329EC8E6B6D51BE757B8C1357BEE58E0147C7ADF31CE9E8719A501BC81E9, withdrawal_credentials=0x010000000000000000000000662F3F39FEDB7C57C2FA855E6BD234BE52595728, signature=0x9543FF9777529607ED590DF1D069AD018F081B97CCD846D1656B8047FBF0520A424C959B0A38BB75ED8D0D739BC415DB0AE597542772AA532E4CE47F8C2D64EF3FA3B20EF85D4A0BD2656734FF1770265AF047419D5EB1BE40147BD445221518, deposit_data_root=5C678593A5CAAE16940ACBAEAAA478E8900BCB8AA8A6745EEC9915272209847D )
-
Null: 0x000...002.ad1fe46a( )
-
Null: 0x000...002.9543ff97( )
-
Null: 0x000...002.3fa3b20e( )
-
Null: 0x000...002.2a92344a( )
-
Null: 0x000...002.bac7d56e( )
-
Null: 0x000...002.00405973( )
-
Null: 0x000...002.3ecc5e11( )
-
Null: 0x000...002.699ca201( )
-
deposit[AbyssEth2Depositor (ln:58)]
deposit[AbyssEth2Depositor (ln:76)]
DepositEvent[AbyssEth2Depositor (ln:83)]
File 1 of 2: AbyssEth2Depositor
File 2 of 2: DepositContract
/* ░█████╗░██████╗░██╗░░░██╗░██████╗░██████╗ ███████╗██╗███╗░░██╗░█████╗░███╗░░██╗░█████╗░███████╗ ██╔══██╗██╔══██╗╚██╗░██╔╝██╔════╝██╔════╝ ██╔════╝██║████╗░██║██╔══██╗████╗░██║██╔══██╗██╔════╝ ███████║██████╦╝░╚████╔╝░╚█████╗░╚█████╗░ █████╗░░██║██╔██╗██║███████║██╔██╗██║██║░░╚═╝█████╗░░ ██╔══██║██╔══██╗░░╚██╔╝░░░╚═══██╗░╚═══██╗ ██╔══╝░░██║██║╚████║██╔══██║██║╚████║██║░░██╗██╔══╝░░ ██║░░██║██████╦╝░░░██║░░░██████╔╝██████╔╝ ██║░░░░░██║██║░╚███║██║░░██║██║░╚███║╚█████╔╝███████╗ ╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═════╝░╚═════╝░ ╚═╝░░░░░╚═╝╚═╝░░╚══╝╚═╝░░╚═╝╚═╝░░╚══╝░╚════╝░╚══════╝ */ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../contracts/interfaces/IDepositContract.sol"; contract AbyssEth2Depositor is ReentrancyGuard, Pausable, Ownable { /** * @dev Eth2 Deposit Contract address. */ IDepositContract public depositContract; /** * @dev Minimal and maximum amount of nodes per transaction. */ uint256 public constant nodesMinAmount = 1; uint256 public constant nodesMaxAmount = 100; uint256 public constant pubkeyLength = 48; uint256 public constant credentialsLength = 32; uint256 public constant signatureLength = 96; /** * @dev Collateral size of one node. */ uint256 public constant collateral = 32 ether; /** * @dev Setting Eth2 Smart Contract address during construction. */ constructor(bool mainnet, address depositContract_) { if (mainnet == true) { depositContract = IDepositContract(0x00000000219ab540356cBB839Cbe05303d7705Fa); } else if (depositContract_ == 0x0000000000000000000000000000000000000000) { depositContract = IDepositContract(0x8c5fecdC472E27Bc447696F431E425D02dd46a8c); } else { depositContract = IDepositContract(depositContract_); } } /** * @dev This contract will not accept direct ETH transactions. */ receive() external payable { revert("AbyssEth2Depositor: do not send ETH directly here"); } /** * @dev Function that allows to deposit up to 100 nodes at once. * * - pubkeys - Array of BLS12-381 public keys. * - withdrawal_credentials - Array of commitments to a public keys for withdrawals. * - signatures - Array of BLS12-381 signatures. * - deposit_data_roots - Array of the SHA-256 hashes of the SSZ-encoded DepositData objects. */ function deposit( bytes[] calldata pubkeys, bytes[] calldata withdrawal_credentials, bytes[] calldata signatures, bytes32[] calldata deposit_data_roots ) external payable whenNotPaused { uint256 nodesAmount = pubkeys.length; require(nodesAmount > 0 && nodesAmount <= 100, "AbyssEth2Depositor: you can deposit only 1 to 100 nodes per transaction"); require(msg.value == collateral * nodesAmount, "AbyssEth2Depositor: the amount of ETH does not match the amount of nodes"); require( withdrawal_credentials.length == nodesAmount && signatures.length == nodesAmount && deposit_data_roots.length == nodesAmount, "AbyssEth2Depositor: amount of parameters do no match"); for (uint256 i = 0; i < nodesAmount; ++i) { require(pubkeys[i].length == pubkeyLength, "AbyssEth2Depositor: wrong pubkey"); require(withdrawal_credentials[i].length == credentialsLength, "AbyssEth2Depositor: wrong withdrawal credentials"); require(signatures[i].length == signatureLength, "AbyssEth2Depositor: wrong signatures"); IDepositContract(address(depositContract)).deposit{value: collateral}( pubkeys[i], withdrawal_credentials[i], signatures[i], deposit_data_roots[i] ); } emit DepositEvent(msg.sender, nodesAmount); } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function pause() public onlyOwner { _pause(); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function unpause() public onlyOwner { _unpause(); } event DepositEvent(address from, uint256 nodesAmount); } // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; // 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); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ constructor () { _paused = false; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { require(!paused(), "Pausable: paused"); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { require(paused(), "Pausable: not paused"); _; } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor () { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } }
File 2 of 2: DepositContract
// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ // ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓ // ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛ // ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━ // ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓ // ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // SPDX-License-Identifier: CC0-1.0 pragma solidity 0.6.11; // 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 DepositContract is IDepositContract, ERC165 { 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; constructor() public { // 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])); } 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) )); } function get_deposit_count() override external view returns (bytes memory) { return to_little_endian_64(uint64(deposit_count)); } 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"); // Check deposit amount require(msg.value >= 1 ether, "DepositContract: deposit value too low"); require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); uint deposit_amount = msg.value / 1 gwei; require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); // Emit `DepositEvent` log bytes memory amount = to_little_endian_64(uint64(deposit_amount)); 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; } 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]; } }