Skip to content

Commit 3b74317

Browse files
Merge pull request #4 from shutter-network/shutter-api
Shutter api
2 parents 9337160 + e5f6733 commit 3b74317

File tree

9 files changed

+886
-1
lines changed

9 files changed

+886
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ gnoshcontracts/
44
shopcontracts/
55
out/
66
.tool-versions
7+
.env

bindings/shutterregistry/shutterregistry.go

Lines changed: 609 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
src = "src"
33
out = "out"
44
libs = ["lib"]
5-
solc = "0.8.22"
5+
solc = "0.8.28"
66

77
extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout']
88
bytecode_hash = 'none'

gen_bindings.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CONTRACTS=(
88
"EonKeyPublish"
99
"KeyBroadcastContract"
1010
"Inbox"
11+
"ShutterRegistry"
1112
)
1213
OUTPUT_DIR="bindings"
1314
PACKAGE_NAME="bindings"

script/Deploy.service.s.sol

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "forge-std/Script.sol";
5+
import "../src/common/KeyBroadcastContract.sol";
6+
import "../src/common/KeyperSet.sol";
7+
import "../src/common/KeyperSetManager.sol";
8+
import "../src/shutter-service/ShutterRegistry.sol";
9+
10+
contract Deploy is Script {
11+
function deployKeyperSetManager(
12+
address deployerAddress
13+
) public returns (KeyperSetManager) {
14+
KeyperSetManager ksm = new KeyperSetManager(deployerAddress);
15+
ksm.initialize(deployerAddress, deployerAddress);
16+
console.log("keyper set manager initialised");
17+
18+
// add bootstrap keyper set
19+
KeyperSet fakeKeyperset = new KeyperSet();
20+
fakeKeyperset.setFinalized();
21+
ksm.addKeyperSet(0, address(fakeKeyperset));
22+
23+
console.log("KeyperSetManager:", address(ksm));
24+
return ksm;
25+
}
26+
27+
function deployKeyBroadcastContract(
28+
KeyperSetManager ksm
29+
) public returns (KeyBroadcastContract) {
30+
KeyBroadcastContract kbc = new KeyBroadcastContract(address(ksm));
31+
console.log("KeyBroadcastContract:", address(kbc));
32+
return kbc;
33+
}
34+
35+
function deployRegistry() public returns (ShutterRegistry) {
36+
ShutterRegistry s = new ShutterRegistry();
37+
console.log("Registry:", address(s));
38+
return s;
39+
}
40+
41+
function run() external {
42+
uint256 deployKey = vm.envUint("DEPLOY_KEY");
43+
address deployerAddress = vm.addr(deployKey);
44+
console.log("Deployer:", deployerAddress);
45+
vm.startBroadcast(deployKey);
46+
47+
KeyperSetManager ksm = deployKeyperSetManager(deployerAddress);
48+
deployKeyBroadcastContract(ksm);
49+
deployRegistry();
50+
51+
vm.stopBroadcast();
52+
}
53+
}

script/DeployRegistry.service.s.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "forge-std/Script.sol";
5+
import "../src/shutter-service/ShutterRegistry.sol";
6+
7+
contract Deploy is Script {
8+
function run() external {
9+
uint256 deployKey = vm.envUint("DEPLOY_KEY");
10+
address deployerAddress = vm.addr(deployKey);
11+
console.log("Deployer:", deployerAddress);
12+
vm.startBroadcast(deployKey);
13+
deploySequencer();
14+
vm.stopBroadcast();
15+
}
16+
17+
function deploySequencer() public returns (ShutterRegistry) {
18+
ShutterRegistry s = new ShutterRegistry();
19+
console.log("ShutterRegistry:", address(s));
20+
return s;
21+
}
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "forge-std/Script.sol";
5+
import {ShutterRegistry} from "src/shutter-service/ShutterRegistry.sol";
6+
7+
contract SubmitTransaction is Script {
8+
function run() external {
9+
uint256 privateKey = vm.envUint("TX_SENDER_KEY");
10+
ShutterRegistry registry = ShutterRegistry(
11+
vm.envAddress("REGISTRY_ADDRESS")
12+
);
13+
uint64 eon = uint64(vm.envUint("EON"));
14+
bytes32 identityPrefix = vm.envBytes32("IDENTITY_PREFIX");
15+
uint64 ts = uint64(vm.envUint("TIMESTAMP"));
16+
17+
vm.startBroadcast(privateKey);
18+
registry.register(eon, identityPrefix, ts);
19+
vm.stopBroadcast();
20+
}
21+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import "openzeppelin/contracts/access/Ownable.sol";
5+
6+
/**
7+
* @title ShutterRegistry
8+
* @dev A contract for managing the registration of identities with timestamps, ensuring unique and future-dated registrations.
9+
* Inherits from OpenZeppelin's Ownable contract to enable ownership-based access control.
10+
*/
11+
contract ShutterRegistry is Ownable {
12+
// Custom error for when an identity is already registered.
13+
error AlreadyRegistered();
14+
15+
// Custom error for when a provided timestamp is in the past.
16+
error TimestampInThePast();
17+
18+
// Custom error for when a identityPrefix provided is empty.
19+
error InvalidIdentityPrefix();
20+
21+
struct RegistrationData {
22+
uint64 eon;
23+
uint64 timestamp;
24+
}
25+
/**
26+
* @dev Mapping to store registration data for each identity.
27+
* The identity is represented as a `bytes32` hash and mapped to struct RegistrationData.
28+
*/
29+
mapping(bytes32 identity => RegistrationData) public registrations;
30+
31+
/**
32+
* @dev Emitted when a new identity is successfully registered.
33+
* @param eon The eon associated with the identity.
34+
* @param identityPrefix The raw prefix input used to derive the registered identity hash.
35+
* @param sender The address of the account that performed the registration.
36+
* @param timestamp The timestamp associated with the registered identity.
37+
*/
38+
event IdentityRegistered(
39+
uint64 eon,
40+
bytes32 identityPrefix,
41+
address sender,
42+
uint64 timestamp
43+
);
44+
45+
/**
46+
* @dev Initializes the contract and assigns ownership to the deployer.
47+
*/
48+
constructor() Ownable(msg.sender) {}
49+
50+
/**
51+
* @notice Registers a new identity with a specified timestamp and eon.
52+
* @dev The identity is derived by hashing the provided `identityPrefix` concatenated with the sender's address.
53+
* @param eon The eon associated with the identity.
54+
* @param identityPrefix The input used to derive the identity hash.
55+
* @param timestamp The future timestamp to be associated with the identity.
56+
* @custom:requirements
57+
* - The identity must not already be registered.
58+
* - The provided timestamp must not be in the past.
59+
*/
60+
function register(
61+
uint64 eon,
62+
bytes32 identityPrefix,
63+
uint64 timestamp
64+
) external {
65+
// Ensure the timestamp is not in the past.
66+
require(timestamp >= block.timestamp, TimestampInThePast());
67+
68+
// Ensure identityPrefix passed in correct.
69+
require(identityPrefix != bytes32(0), InvalidIdentityPrefix());
70+
71+
// Generate the identity hash from the provided prefix and the sender's address.
72+
bytes32 identity = keccak256(
73+
abi.encodePacked(identityPrefix, msg.sender)
74+
);
75+
RegistrationData storage registrationData = registrations[identity];
76+
// Ensure the identity is not already registered.
77+
require(registrationData.timestamp == 0, AlreadyRegistered());
78+
79+
// Store the registration timestamp.
80+
registrationData.eon = eon;
81+
registrationData.timestamp = timestamp;
82+
83+
// Emit the IdentityRegistered event.
84+
emit IdentityRegistered(eon, identityPrefix, msg.sender, timestamp);
85+
}
86+
}

test/ShutterRegistry.t.sol

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.22;
3+
4+
import "forge-std/Test.sol";
5+
import "../src/shutter-service/ShutterRegistry.sol";
6+
7+
contract ShutterRegistryTest is Test {
8+
ShutterRegistry public shutterRegistry;
9+
10+
function setUp() public {
11+
shutterRegistry = new ShutterRegistry();
12+
}
13+
14+
function testIdentityRegistration() public {
15+
uint64 eon = 5;
16+
bytes32 identityPrefix = hex"001122";
17+
uint64 timestamp = uint64(block.timestamp) + 100;
18+
address sender = makeAddr("sender");
19+
20+
vm.expectEmit(address(shutterRegistry));
21+
emit ShutterRegistry.IdentityRegistered(
22+
eon,
23+
identityPrefix,
24+
sender,
25+
timestamp
26+
);
27+
28+
hoax(sender);
29+
shutterRegistry.register(eon, identityPrefix, timestamp);
30+
31+
bytes32 identity = keccak256(abi.encodePacked(identityPrefix, sender));
32+
(uint64 registeredEon, uint64 registeredTimestamp) = shutterRegistry
33+
.registrations(identity);
34+
35+
//verifying registered timestamp
36+
assertEqUint(registeredEon, eon);
37+
assertEqUint(registeredTimestamp, timestamp);
38+
}
39+
40+
function testDuplicateRegistration() public {
41+
uint64 eon = 5;
42+
bytes32 identityPrefix = hex"001122";
43+
uint64 timestamp = uint64(block.timestamp) + 100;
44+
address sender = makeAddr("sender");
45+
46+
vm.expectEmit(address(shutterRegistry));
47+
emit ShutterRegistry.IdentityRegistered(
48+
eon,
49+
identityPrefix,
50+
sender,
51+
timestamp
52+
);
53+
54+
hoax(sender);
55+
shutterRegistry.register(eon, identityPrefix, timestamp);
56+
57+
uint64 newTimestamp = uint64(block.timestamp) + 200;
58+
vm.expectRevert(ShutterRegistry.AlreadyRegistered.selector);
59+
hoax(sender);
60+
shutterRegistry.register(eon, identityPrefix, newTimestamp);
61+
62+
//verifying registered timestamp
63+
bytes32 identity = keccak256(abi.encodePacked(identityPrefix, sender));
64+
(, uint64 registeredTimestamp) = shutterRegistry.registrations(
65+
identity
66+
);
67+
assertEqUint(registeredTimestamp, timestamp);
68+
}
69+
70+
function testInvalidTimestamp() public {
71+
uint64 eon = 5;
72+
bytes32 identityPrefix = hex"001122";
73+
uint64 timestamp = uint64(block.timestamp) - 1;
74+
address sender = makeAddr("sender");
75+
76+
vm.expectRevert(ShutterRegistry.TimestampInThePast.selector);
77+
hoax(sender);
78+
shutterRegistry.register(eon, identityPrefix, timestamp);
79+
}
80+
81+
function testMissingIdentity() public {
82+
uint64 eon = 5;
83+
// zero bytes for identity prefix should fail
84+
bytes32 identityPrefix = hex"00";
85+
uint64 timestamp = uint64(block.timestamp) + 100;
86+
address sender = makeAddr("sender");
87+
88+
vm.expectRevert(ShutterRegistry.InvalidIdentityPrefix.selector);
89+
hoax(sender);
90+
shutterRegistry.register(eon, identityPrefix, timestamp);
91+
}
92+
}

0 commit comments

Comments
 (0)