Skip to content

Commit

Permalink
feat: add initial version of ERC6538Registry and tests (#3)
Browse files Browse the repository at this point in the history
Adds a first implementation of the ERC-6538 Registry contract. It does not yet implement the `registerKeysOnBehalf` methods, which will be done in a future PR.
  • Loading branch information
mds1 authored Jul 20, 2023
1 parent b8222da commit 145d7f2
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 2 deletions.
5 changes: 5 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ pragma solidity 0.8.20;

import {Script} from "forge-std/Script.sol";
import {ERC5564Announcer} from "src/ERC5564Announcer.sol";
import {ERC6538Registry} from "src/ERC6538Registry.sol";

contract Deploy is Script {
ERC5564Announcer announcer;
ERC6538Registry registry;

function run() public {
vm.broadcast();
announcer = new ERC5564Announcer();

vm.broadcast();
registry = new ERC6538Registry();
}
}
45 changes: 45 additions & 0 deletions src/ERC6538Registry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {IERC6538Registry} from "./interfaces/IERC6538Registry.sol";

/// @dev `ERC6538Registry` contract to map accounts to their stealth meta-address. See
/// [ERC-6538](https://eips.ethereum.org/EIPS/eip-6538) to learn more.
contract ERC6538Registry is IERC6538Registry {
/// @notice Maps a registrant's identifier to the scheme ID to the stealth meta-address.
/// @dev `registrant` may be a standard 160-bit address or any other identifier.
/// @dev `schemeId` is an integer identifier for the stealth address scheme.
mapping(bytes registrant => mapping(uint256 schemeId => bytes)) public stealthMetaAddressOf;

/// @inheritdoc IERC6538Registry
function registerKeys(uint256 schemeId, bytes memory stealthMetaAddress) external {
bytes memory registrant = _toBytes(msg.sender);
stealthMetaAddressOf[registrant][schemeId] = stealthMetaAddress;
emit StealthMetaAddressSet(registrant, schemeId, stealthMetaAddress);
}

/// @inheritdoc IERC6538Registry
function registerKeysOnBehalf(
address registrant,
uint256 schemeId,
bytes memory signature,
bytes memory stealthMetaAddress
) external pure {
registerKeysOnBehalf(_toBytes(registrant), schemeId, signature, stealthMetaAddress);
}

/// @inheritdoc IERC6538Registry
function registerKeysOnBehalf(
bytes memory, // registrant
uint256, // schemeId
bytes memory, // signature
bytes memory // stealthMetaAddress
) public pure {
revert("not implemented");
}

/// @dev Converts an `address` to `bytes`.
function _toBytes(address who) internal pure returns (bytes memory) {
return bytes.concat(bytes32(uint256(uint160(who))));
}
}
5 changes: 3 additions & 2 deletions src/interfaces/IERC5564Announcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pragma solidity ^0.8.0;
/// [ERC-5564](https://eips.ethereum.org/EIPS/eip-5564) to learn more.
interface IERC5564Announcer {
/// @dev Emitted when something is sent to a stealth address.
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 0 for
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 1 for
/// secp256k1, as specified in ERC-5564.
/// @param stealthAddress The computed stealth address for the recipient.
/// @param caller The caller of the `announce` function that emitted this event.
Expand All @@ -24,7 +24,8 @@ interface IERC5564Announcer {
);

/// @dev Called by integrators to emit an `Announcement` event.
/// @param schemeId The applied stealth address scheme (such as secp25k1).
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 1 for
/// secp256k1, as specified in ERC-5564.
/// @param stealthAddress The computed stealth address for the recipient.
/// @param ephemeralPubKey Ephemeral public key used by the sender.
/// @param metadata Arbitrary data to emit with the event. The first byte MUST be the view tag.
Expand Down
57 changes: 57 additions & 0 deletions src/interfaces/IERC6538Registry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/// @dev Interface for calling the `ERC6538Registry` contract to map accounts to their stealth
/// meta-address. See [ERC-6538](https://eips.ethereum.org/EIPS/eip-6538) to learn more.
interface IERC6538Registry {
/// @dev Emitted when a registrant updates their stealth meta-address.
/// @param registrant The account that registered the stealth meta-address.
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 0 for
/// secp256k1, as specified in ERC-5564.
/// @param stealthMetaAddress The stealth meta-address.
/// [ERC-5564](https://eips.ethereum.org/EIPS/eip-5564) bases the format for stealth
/// meta-addresses on [ERC-3770](https://eips.ethereum.org/EIPS/eip-3770) and specifies them as:
/// st:<shortName>:0x<spendingPubKey>:<viewingPubKey>
/// The chain (`shortName`) is implicit based on the chain the `ERC6538Registry` is deployed on,
/// therefore this `stealthMetaAddress` is just the `spendingPubKey` and `viewingPubKey`
/// concatenated.
event StealthMetaAddressSet(
bytes indexed registrant, uint256 indexed schemeId, bytes stealthMetaAddress
);

/// @notice Sets the caller's stealth meta-address for the given scheme ID.
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 0 for
/// secp256k1, as specified in ERC-5564.
/// @param stealthMetaAddress The stealth meta-address to register.
function registerKeys(uint256 schemeId, bytes memory stealthMetaAddress) external;

/// @notice Sets the `registrant`'s stealth meta-address for the given scheme ID.
/// @param registrant Address of the registrant.
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 0 for
/// secp256k1, as specified in ERC-5564.
/// @param signature A signature from the `registrant` authorizing the registration.
/// @param stealthMetaAddress The stealth meta-address to register.
/// @dev Supports both EOA signatures and EIP-1271 signatures.
/// @dev Reverts if the signature is invalid.
function registerKeysOnBehalf(
address registrant,
uint256 schemeId,
bytes memory signature,
bytes memory stealthMetaAddress
) external;

/// @notice Sets the `registrant`s stealth meta-address for the given scheme ID.
/// @param registrant Recipient identifier, such as an address.
/// @param schemeId Identifier corresponding to the applied stealth address scheme, e.g. 0 for
/// secp256k1, as specified in ERC-5564.
/// @param signature A signature from the `registrant` authorizing the registration.
/// @param stealthMetaAddress The stealth meta-address to register.
/// @dev Supports both EOA signatures and EIP-1271 signatures.
/// @dev Reverts if the signature is invalid.
function registerKeysOnBehalf(
bytes memory registrant,
uint256 schemeId,
bytes memory signature,
bytes memory stealthMetaAddress
) external;
}
70 changes: 70 additions & 0 deletions test/ERC6538Registry.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";
import {Deploy} from "script/Deploy.s.sol";

contract ERC6538RegistryTest is Test, Deploy {
event StealthMetaAddressSet(
bytes indexed registrant, uint256 indexed schemeId, bytes stealthMetaAddress
);

function setUp() public {
Deploy.run();
}

function toBytes(address who) internal pure returns (bytes memory) {
return bytes.concat(bytes32(uint256(uint160(who))));
}
}

contract RegisterKeys is ERC6538RegistryTest {
function testFuzz_EmitsStealthMetaAddressSetEvent(
address caller,
uint256 schemeId,
bytes memory stealthMetaAddress
) external {
vm.prank(caller);
vm.expectEmit();
emit StealthMetaAddressSet(toBytes(caller), schemeId, stealthMetaAddress);
registry.registerKeys(schemeId, stealthMetaAddress);
}

function testFuzz_CorrectlyMapsRegistrantToSchemeIdToStealthMetaAddressInStorage(
address caller,
uint256 schemeId,
bytes memory stealthMetaAddress
) external {
assertEq(registry.stealthMetaAddressOf(toBytes(caller), schemeId), "");
vm.prank(caller);
registry.registerKeys(schemeId, stealthMetaAddress);
assertEq(registry.stealthMetaAddressOf(toBytes(caller), schemeId), stealthMetaAddress);
}

// This test is a subset of `testFuzz_EmitsStealthMetaAddressSetEvent`, and is mainly present to
// make the `announce` method's specification more explicit. For this reason, we set the number of
// runs to 1 for all profiles.
/// forge-config: default.fuzz.runs = 1
/// forge-config: ci.fuzz.runs = 1
/// forge-config: lite.fuzz.runs = 1
function testFuzz_NeverReverts(address caller, uint256 schemeId, bytes memory stealthMetaAddress)
external
{
vm.prank(caller);
registry.registerKeys(schemeId, stealthMetaAddress);
}
}

contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest {
function test_NotImplemented() external {
vm.expectRevert("not implemented");
registry.registerKeysOnBehalf(address(0), 0, "", "");
}
}

contract RegisterKeysOnBehalf_Bytes is ERC6538RegistryTest {
function test_NotImplemented() external {
vm.expectRevert("not implemented");
registry.registerKeysOnBehalf(bytes("0"), 0, "", "");
}
}

0 comments on commit 145d7f2

Please sign in to comment.