Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ gnoshcontracts/
shopcontracts/
out/
.tool-versions
.env
609 changes: 609 additions & 0 deletions bindings/shutterregistry/shutterregistry.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.22"
solc = "0.8.28"

extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout']
bytecode_hash = 'none'
Expand Down
1 change: 1 addition & 0 deletions gen_bindings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CONTRACTS=(
"EonKeyPublish"
"KeyBroadcastContract"
"Inbox"
"ShutterRegistry"
)
OUTPUT_DIR="bindings"
PACKAGE_NAME="bindings"
Expand Down
53 changes: 53 additions & 0 deletions script/Deploy.service.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import "../src/common/KeyBroadcastContract.sol";
import "../src/common/KeyperSet.sol";
import "../src/common/KeyperSetManager.sol";
import "../src/shutter-service/ShutterRegistry.sol";

contract Deploy is Script {
function deployKeyperSetManager(
address deployerAddress
) public returns (KeyperSetManager) {
KeyperSetManager ksm = new KeyperSetManager(deployerAddress);
ksm.initialize(deployerAddress, deployerAddress);
console.log("keyper set manager initialised");

// add bootstrap keyper set
KeyperSet fakeKeyperset = new KeyperSet();
fakeKeyperset.setFinalized();
ksm.addKeyperSet(0, address(fakeKeyperset));

console.log("KeyperSetManager:", address(ksm));
return ksm;
}

function deployKeyBroadcastContract(
KeyperSetManager ksm
) public returns (KeyBroadcastContract) {
KeyBroadcastContract kbc = new KeyBroadcastContract(address(ksm));
console.log("KeyBroadcastContract:", address(kbc));
return kbc;
}

function deployRegistry() public returns (ShutterRegistry) {
ShutterRegistry s = new ShutterRegistry();
console.log("Registry:", address(s));
return s;
}

function run() external {
uint256 deployKey = vm.envUint("DEPLOY_KEY");
address deployerAddress = vm.addr(deployKey);
console.log("Deployer:", deployerAddress);
vm.startBroadcast(deployKey);

KeyperSetManager ksm = deployKeyperSetManager(deployerAddress);
deployKeyBroadcastContract(ksm);
deployRegistry();

vm.stopBroadcast();
}
}
22 changes: 22 additions & 0 deletions script/DeployRegistry.service.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import "../src/shutter-service/ShutterRegistry.sol";

contract Deploy is Script {
function run() external {
uint256 deployKey = vm.envUint("DEPLOY_KEY");
address deployerAddress = vm.addr(deployKey);
console.log("Deployer:", deployerAddress);
vm.startBroadcast(deployKey);
deploySequencer();
vm.stopBroadcast();
}

function deploySequencer() public returns (ShutterRegistry) {
ShutterRegistry s = new ShutterRegistry();
console.log("ShutterRegistry:", address(s));
return s;
}
}
21 changes: 21 additions & 0 deletions script/SubmitTransaction.service.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import {ShutterRegistry} from "src/shutter-service/ShutterRegistry.sol";

contract SubmitTransaction is Script {
function run() external {
uint256 privateKey = vm.envUint("TX_SENDER_KEY");
ShutterRegistry registry = ShutterRegistry(
vm.envAddress("REGISTRY_ADDRESS")
);
uint64 eon = uint64(vm.envUint("EON"));
bytes32 identityPrefix = vm.envBytes32("IDENTITY_PREFIX");
uint64 ts = uint64(vm.envUint("TIMESTAMP"));

vm.startBroadcast(privateKey);
registry.register(eon, identityPrefix, ts);
vm.stopBroadcast();
}
}
86 changes: 86 additions & 0 deletions src/shutter-service/ShutterRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "openzeppelin/contracts/access/Ownable.sol";

/**
* @title ShutterRegistry
* @dev A contract for managing the registration of identities with timestamps, ensuring unique and future-dated registrations.
* Inherits from OpenZeppelin's Ownable contract to enable ownership-based access control.
*/
contract ShutterRegistry is Ownable {
// Custom error for when an identity is already registered.
error AlreadyRegistered();

// Custom error for when a provided timestamp is in the past.
error TimestampInThePast();

// Custom error for when a identityPrefix provided is empty.
error InvalidIdentityPrefix();

struct RegistrationData {
uint64 eon;
uint64 timestamp;
}
/**
* @dev Mapping to store registration data for each identity.
* The identity is represented as a `bytes32` hash and mapped to struct RegistrationData.
*/
mapping(bytes32 identity => RegistrationData) public registrations;

/**
* @dev Emitted when a new identity is successfully registered.
* @param eon The eon associated with the identity.
* @param identityPrefix The raw prefix input used to derive the registered identity hash.
* @param sender The address of the account that performed the registration.
* @param timestamp The timestamp associated with the registered identity.
*/
event IdentityRegistered(
uint64 eon,
bytes32 identityPrefix,
address sender,
uint64 timestamp
);

/**
* @dev Initializes the contract and assigns ownership to the deployer.
*/
constructor() Ownable(msg.sender) {}

/**
* @notice Registers a new identity with a specified timestamp and eon.
* @dev The identity is derived by hashing the provided `identityPrefix` concatenated with the sender's address.
* @param eon The eon associated with the identity.
* @param identityPrefix The input used to derive the identity hash.
* @param timestamp The future timestamp to be associated with the identity.
* @custom:requirements
* - The identity must not already be registered.
* - The provided timestamp must not be in the past.
*/
function register(
uint64 eon,
bytes32 identityPrefix,
uint64 timestamp
) external {
// Ensure the timestamp is not in the past.
require(timestamp >= block.timestamp, TimestampInThePast());

// Ensure identityPrefix passed in correct.
require(identityPrefix != bytes32(0), InvalidIdentityPrefix());

// Generate the identity hash from the provided prefix and the sender's address.
bytes32 identity = keccak256(
abi.encodePacked(identityPrefix, msg.sender)
);
RegistrationData storage registrationData = registrations[identity];
// Ensure the identity is not already registered.
require(registrationData.timestamp == 0, AlreadyRegistered());

// Store the registration timestamp.
registrationData.eon = eon;
registrationData.timestamp = timestamp;

// Emit the IdentityRegistered event.
emit IdentityRegistered(eon, identityPrefix, msg.sender, timestamp);
}
}
92 changes: 92 additions & 0 deletions test/ShutterRegistry.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "forge-std/Test.sol";
import "../src/shutter-service/ShutterRegistry.sol";

contract ShutterRegistryTest is Test {
ShutterRegistry public shutterRegistry;

function setUp() public {
shutterRegistry = new ShutterRegistry();
}

function testIdentityRegistration() public {
uint64 eon = 5;
bytes32 identityPrefix = hex"001122";
uint64 timestamp = uint64(block.timestamp) + 100;
address sender = makeAddr("sender");

vm.expectEmit(address(shutterRegistry));
emit ShutterRegistry.IdentityRegistered(
eon,
identityPrefix,
sender,
timestamp
);

hoax(sender);
shutterRegistry.register(eon, identityPrefix, timestamp);

bytes32 identity = keccak256(abi.encodePacked(identityPrefix, sender));
(uint64 registeredEon, uint64 registeredTimestamp) = shutterRegistry
.registrations(identity);

//verifying registered timestamp
assertEqUint(registeredEon, eon);
assertEqUint(registeredTimestamp, timestamp);
}

function testDuplicateRegistration() public {
uint64 eon = 5;
bytes32 identityPrefix = hex"001122";
uint64 timestamp = uint64(block.timestamp) + 100;
address sender = makeAddr("sender");

vm.expectEmit(address(shutterRegistry));
emit ShutterRegistry.IdentityRegistered(
eon,
identityPrefix,
sender,
timestamp
);

hoax(sender);
shutterRegistry.register(eon, identityPrefix, timestamp);

uint64 newTimestamp = uint64(block.timestamp) + 200;
vm.expectRevert(ShutterRegistry.AlreadyRegistered.selector);
hoax(sender);
shutterRegistry.register(eon, identityPrefix, newTimestamp);

//verifying registered timestamp
bytes32 identity = keccak256(abi.encodePacked(identityPrefix, sender));
(, uint64 registeredTimestamp) = shutterRegistry.registrations(
identity
);
assertEqUint(registeredTimestamp, timestamp);
}

function testInvalidTimestamp() public {
uint64 eon = 5;
bytes32 identityPrefix = hex"001122";
uint64 timestamp = uint64(block.timestamp) - 1;
address sender = makeAddr("sender");

vm.expectRevert(ShutterRegistry.TimestampInThePast.selector);
hoax(sender);
shutterRegistry.register(eon, identityPrefix, timestamp);
}

function testMissingIdentity() public {
uint64 eon = 5;
// zero bytes for identity prefix should fail
bytes32 identityPrefix = hex"00";
uint64 timestamp = uint64(block.timestamp) + 100;
address sender = makeAddr("sender");

vm.expectRevert(ShutterRegistry.InvalidIdentityPrefix.selector);
hoax(sender);
shutterRegistry.register(eon, identityPrefix, timestamp);
}
}
Loading