diff --git a/contracts/interfaces/ISFC.sol b/contracts/interfaces/ISFC.sol index 072c3d9..0650e7d 100644 --- a/contracts/interfaces/ISFC.sol +++ b/contracts/interfaces/ISFC.sol @@ -63,6 +63,8 @@ interface ISFC { function getValidatorPubkey(uint256) external view returns (bytes memory); + function pubkeyAddressToValidatorID(address pubkeyAddress) external view returns (uint256); + function getWithdrawalRequest( address, uint256, diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index e63d546..e764a48 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -109,8 +109,8 @@ contract SFC is Initializable, Ownable, Version { // the governance contract (to recalculate votes when the stake changes) address public voteBookAddress; - // keccak256(pubkey bytes) => validator ID (prevents using the same key by multiple validators) - mapping(bytes32 pubkeyHash => uint256 validatorID) internal pubkeyHashToValidatorID; + // address derived from the validator pubkey => validator id + mapping(address pubkeyAddress => uint256 validatorID) public pubkeyAddressToValidatorID; // address authorized to initiate redirection address public redirectionAuthorizer; @@ -358,7 +358,7 @@ contract SFC is Initializable, Ownable, Version { if (pubkey.length != 66 || pubkey[0] != 0xc0) { revert MalformedPubkey(); } - if (pubkeyHashToValidatorID[keccak256(pubkey)] != 0) { + if (pubkeyAddressToValidatorID[_pubkeyToAddress(pubkey)] != 0) { revert PubkeyUsedByOtherValidator(); } _createValidator(msg.sender, pubkey); @@ -932,7 +932,7 @@ contract SFC is Initializable, Ownable, Version { } /// Create a new validator. - function _createValidator(address auth, bytes memory pubkey) internal { + function _createValidator(address auth, bytes calldata pubkey) internal { uint256 validatorID = ++lastValidatorID; _rawCreateValidator(auth, validatorID, pubkey, OK_STATUS, currentEpoch(), _now(), 0, 0); } @@ -941,7 +941,7 @@ contract SFC is Initializable, Ownable, Version { function _rawCreateValidator( address auth, uint256 validatorID, - bytes memory pubkey, + bytes calldata pubkey, uint256 status, uint256 createdEpoch, uint256 createdTime, @@ -959,7 +959,7 @@ contract SFC is Initializable, Ownable, Version { getValidator[validatorID].deactivatedEpoch = deactivatedEpoch; getValidator[validatorID].auth = auth; getValidatorPubkey[validatorID] = pubkey; - pubkeyHashToValidatorID[keccak256(pubkey)] = validatorID; + pubkeyAddressToValidatorID[_pubkeyToAddress(pubkey)] = validatorID; emit CreatedValidator(validatorID, auth, createdEpoch, createdTime); if (deactivatedEpoch != 0) { @@ -1068,6 +1068,11 @@ contract SFC is Initializable, Ownable, Version { return (rawReward * commission) / Decimal.unit(); } + /// Derive address from validator private key + function _pubkeyToAddress(bytes calldata pubkey) private pure returns (address) { + return address(uint160(uint256(keccak256(pubkey[2:])))); + } + /// Get current time. function _now() internal view virtual returns (uint256) { return block.timestamp; diff --git a/test/SFC.ts b/test/SFC.ts index 15a5896..3cc2c40 100644 --- a/test/SFC.ts +++ b/test/SFC.ts @@ -394,9 +394,9 @@ describe('SFC', () => { const node = new BlockchainNode(this.sfc); const [validator, secondValidator] = await ethers.getSigners(); const pubkey = - '0xc000a2941866e485442aa6b17d67d77f8a6c4580bb556894cc1618473eff1e18203d8cce50b563cf4c75e408886079b8f067069442ed52e2ac9e556baa3f8fcc525f'; + '0xc0040220af695ae100c370c7acff4f57e5a0c507abbbc8ac6cc2ae0ce3a81747e0cd3c6892233faae1af5d982d05b1c13a0ad4449685f0b5a6138b301cc5263f8316'; const secondPubkey = - '0xc000a2941866e485442aa6b17d67d77f8a6c4580bb556894cc1618473eff1e18203d8cce50b563cf4c75e408886079b8f067069442ed52e2ac9e556baa3f8fcc5251'; + '0xc00499a876465bc626061bb2f0326df1a223c14e3bcdc3fff3deb0f95f316b9d586b03f00bbc2349be3d7908de8626cfd8f7fd6f73bff49df1299f44b6855562c33d'; await this.sfc.enableNonNodeCalls(); expect(await this.sfc.lastValidatorID()).to.equal(0); @@ -428,6 +428,13 @@ describe('SFC', () => { expect(await this.sfc.getValidatorPubkey(firstValidatorID)).to.equal(pubkey); expect(await this.sfc.getValidatorPubkey(secondValidatorID)).to.equal(secondPubkey); + expect(await this.sfc.pubkeyAddressToValidatorID('0x65Db23D0c4FA8Ec58151a30E54e6a6046c97cD10')).to.equal( + firstValidatorID, + ); + expect(await this.sfc.pubkeyAddressToValidatorID('0x1AA3196683eE97Adf5B398875828E322a34E8085')).to.equal( + secondValidatorID, + ); + const firstValidatorObj = await this.sfc.getValidator(firstValidatorID); const secondValidatorObj = await this.sfc.getValidator(secondValidatorID); @@ -695,7 +702,7 @@ describe('SFC', () => { expect(await this.sfc.rewardsStash(this.delegator, this.validatorId)).to.equal(9_180); }); - it('Should succeed andupdate the validator on node', async function () { + it('Should succeed and update the validator on node', async function () { await this.constants.updateOfflinePenaltyThresholdTime(10000); await this.constants.updateOfflinePenaltyThresholdBlocksNum(500);