Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions contracts/contract/megapool/RocketMegapoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ contract RocketMegapoolManager is RocketBase, RocketMegapoolManagerInterface {
setUint(setCountKey, index + 1);
uint256 encoded = (uint256(uint160(_megapoolAddress)) << 96) | _validatorId;
setUint(keccak256(abi.encodePacked("megapool.validator.set", index)), encoded);
// Add pubkey => megapool mapping and ensure uniqueness
bytes32 key = keccak256(abi.encodePacked("validator.megapool", _megapoolAddress, _pubkey));
// Add pubkey => megapool mapping and ensure uniqueness across all megapools
bytes32 key = keccak256(abi.encodePacked("validator.megapool", _pubkey));
require(getAddress(key) == address(0x0), "Pubkey in use");
setAddress(key, _megapoolAddress);
}

/// @notice Returns the megapool that owns a given validator pubkey, or address(0) if unclaimed
/// @param _pubkey The validator pubkey to look up
function getMegapoolByPubkey(bytes calldata _pubkey) override external view returns (address) {
return getAddress(keccak256(abi.encodePacked("validator.megapool", _pubkey)));
}

/// @notice Returns the last trusted member to execute a challenge
function getLastChallenger() override external view returns (address) {
return getAddress(challengerKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface RocketMegapoolManagerInterface {

function getValidatorCount() external view returns (uint256);
function addValidator(address _megapoolAddress, uint32 _validatorId, bytes calldata _pubkey) external;
function getMegapoolByPubkey(bytes calldata _pubkey) external view returns (address);
function getLastChallenger() external view returns (address);
function getValidatorInfo(uint256 _index) external view returns (bytes memory pubkey, RocketMegapoolStorageLayout.ValidatorInfo memory validatorInfo, address megapool, uint32 validatorId);
function stake(RocketMegapoolInterface megapool, uint32 _validatorId, uint64 _slotTimestamp, ValidatorProof calldata _proof, SlotProof calldata _slotProof) external;
Expand Down
40 changes: 40 additions & 0 deletions test/megapool/megapool-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,46 @@ export default function() {
);
});

it(printTitle('node', 'can not reuse pubkey across megapools'), async () => {
// node performs first deposit with a fresh pubkey
const pubkey = getValidatorPubkey();
const signature = getValidatorSignature();
const wc1 = await getMegapoolWithdrawalCredentials(node.address);
const depositData1 = {
pubkey: pubkey,
withdrawalCredentials: Buffer.from(wc1.substr(2), 'hex'),
amount: BigInt(1000000000), // gwei
signature: signature,
};
const depositDataRoot1 = getDepositDataRoot(depositData1);
const rocketNodeDeposit = await RocketNodeDeposit.deployed();
await rocketNodeDeposit.connect(node).deposit('4'.ether, false, pubkey, signature, depositDataRoot1, { value: '4'.ether });

// node2 (different megapool) attempts to register the same pubkey
const wc2 = await getMegapoolWithdrawalCredentials(node2.address);
const depositData2 = {
pubkey: pubkey,
withdrawalCredentials: Buffer.from(wc2.substr(2), 'hex'),
amount: BigInt(1000000000), // gwei
signature: signature,
};
const depositDataRoot2 = getDepositDataRoot(depositData2);
await shouldRevert(
rocketNodeDeposit.connect(node2).deposit('4'.ether, false, pubkey, signature, depositDataRoot2, { value: '4'.ether }),
'Was able to reuse pubkey from a different megapool',
'Pubkey in use',
);

// Sanity check the new getter resolves the pubkey to node's megapool
const rocketMegapoolManager = await RocketMegapoolManager.deployed();
const expectedMegapool = (await getMegapoolForNode(node)).target;
assert.strictEqual(
(await rocketMegapoolManager.getMegapoolByPubkey(pubkey)).toLowerCase(),
expectedMegapool.toLowerCase(),
'getMegapoolByPubkey did not return the owning megapool',
);
});

it(printTitle('node', 'can deposit using ETH credit'), async () => {
// Enter and exit queue to receive a 4 ETH credit
await nodeDeposit(node, '4'.ether);
Expand Down