diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 73b8bfc..2e5a106 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1 +1,5 @@ -FooTest:test_Example() (gas: 8662) \ No newline at end of file +ScKeystoreTest:test__addUser__addsUser__whenUserInfoIsValid() (gas: 56777) +ScKeystoreTest:test__addUser__reverts__whenUserAlreadyExists() (gas: 61532) +ScKeystoreTest:test__addUser__reverts__whenUserInfoIsMalformed() (gas: 9384) +ScKeystoreTest:test__getUser__returnsUserInfo__whenUserExists() (gas: 60956) +ScKeystoreTest:test__userExists__returnsFalse__whenUserDoesNotExist() (gas: 7976) \ No newline at end of file diff --git a/contracts/script/Deploy.s.sol b/contracts/script/Deploy.s.sol index 68f0689..8d6b318 100644 --- a/contracts/script/Deploy.s.sol +++ b/contracts/script/Deploy.s.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.19 <=0.9.0; -import { Foo } from "../src/Foo.sol"; +import { ScKeystore } from "../src/ScKeystore.sol"; import { BaseScript } from "./Base.s.sol"; import { DeploymentConfig } from "./DeploymentConfig.s.sol"; contract Deploy is BaseScript { - function run() public returns (Foo foo, DeploymentConfig deploymentConfig) { + function run() public returns (ScKeystore scKeystore, DeploymentConfig deploymentConfig) { deploymentConfig = new DeploymentConfig(broadcaster); - foo = new Foo(); + scKeystore = new ScKeystore(); } } diff --git a/contracts/src/Foo.sol b/contracts/src/Foo.sol deleted file mode 100644 index d69be05..0000000 --- a/contracts/src/Foo.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.19; - -contract Foo { - function id(uint256 value) external pure returns (uint256) { - return value; - } -} diff --git a/contracts/src/IScKeystore.sol b/contracts/src/IScKeystore.sol index 237e806..ccc1449 100644 --- a/contracts/src/IScKeystore.sol +++ b/contracts/src/IScKeystore.sol @@ -7,13 +7,13 @@ struct KeyPackage { } struct UserInfo { - KeyPackage[] keyPackages; + uint256[] keyPackageIndices; bytes signaturePubKey; } interface IScKeystore { function userExists(address user) external view returns (bool); - function addUser(UserInfo calldata userInfo) external; + function addUser(bytes calldata signaturePubKey, KeyPackage calldata keyPackage) external; function getUser(address user) external view returns (UserInfo memory); function addKeyPackage(KeyPackage calldata) external; function getAvailableKeyPackage(address user) external view returns (KeyPackage memory); diff --git a/contracts/src/ScKeystore.sol b/contracts/src/ScKeystore.sol new file mode 100644 index 0000000..bea1119 --- /dev/null +++ b/contracts/src/ScKeystore.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.24; + +import { IScKeystore, UserInfo, KeyPackage } from "./IScKeystore.sol"; + +error UserAlreadyExists(); +error MalformedKeyPackage(); +error MalformedUserInfo(); +error UserDoesNotExist(); + +contract ScKeystore is IScKeystore { + event UserAdded(address user, bytes signaturePubKey); + event UserKeyPackageAdded(address indexed user, uint256 index); + + mapping(address user => UserInfo userInfo) private users; + KeyPackage[] private keyPackages; + + function userExists(address user) public view returns (bool) { + return users[user].signaturePubKey.length > 0; + } + + function addUser(bytes calldata signaturePubKey, KeyPackage calldata keyPackage) external { + if (signaturePubKey.length == 0) revert MalformedUserInfo(); + if (keyPackage.data.length == 0) revert MalformedKeyPackage(); + if (userExists(msg.sender)) revert UserAlreadyExists(); + + keyPackages.push(keyPackage); + uint256 keyPackageIndex = keyPackages.length - 1; + + users[msg.sender] = UserInfo(new uint256[](0), signaturePubKey); + users[msg.sender].signaturePubKey = signaturePubKey; + users[msg.sender].keyPackageIndices.push(keyPackageIndex); + + emit UserAdded(msg.sender, signaturePubKey); + } + + function getUser(address user) external view returns (UserInfo memory) { + return users[user]; + } + + function addKeyPackage(KeyPackage calldata keyPackage) external { + if (keyPackage.data.length == 0) revert MalformedKeyPackage(); + if (!userExists(msg.sender)) revert UserDoesNotExist(); + + keyPackages.push(keyPackage); + uint256 keyPackageIndex = keyPackages.length - 1; + users[msg.sender].keyPackageIndices.push(keyPackageIndex); + + emit UserKeyPackageAdded(msg.sender, keyPackageIndex); + } + + function getAvailableKeyPackage(address user) external view returns (KeyPackage memory) { + UserInfo memory userInfo = users[user]; + uint256 keyPackageIndex = userInfo.keyPackageIndices[userInfo.keyPackageIndices.length - 1]; + return keyPackages[keyPackageIndex]; + } + + function getAllKeyPackagesForUser(address user) external view returns (KeyPackage[] memory) { + UserInfo memory userInfo = users[user]; + KeyPackage[] memory userKeyPackages = new KeyPackage[](userInfo.keyPackageIndices.length); + for (uint256 i = 0; i < userInfo.keyPackageIndices.length; i++) { + userKeyPackages[i] = keyPackages[userInfo.keyPackageIndices[i]]; + } + return userKeyPackages; + } +} diff --git a/contracts/test/Foo.t.sol b/contracts/test/Foo.t.sol deleted file mode 100644 index 6b15158..0000000 --- a/contracts/test/Foo.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.19 <0.9.0; - -import { Test, console } from "forge-std/Test.sol"; - -import { Deploy } from "../script/Deploy.s.sol"; -import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; -import { Foo } from "../src/Foo.sol"; - -contract FooTest is Test { - Foo internal foo; - DeploymentConfig internal deploymentConfig; - - address internal deployer; - - function setUp() public virtual { - Deploy deployment = new Deploy(); - (foo, deploymentConfig) = deployment.run(); - } - - function test_Example() external { - console.log("Hello World"); - uint256 x = 42; - assertEq(foo.id(x), x, "value mismatch"); - } -} diff --git a/contracts/test/ScKeystore.t.sol b/contracts/test/ScKeystore.t.sol new file mode 100644 index 0000000..b8a44b6 --- /dev/null +++ b/contracts/test/ScKeystore.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19 <0.9.0; + +import { Test } from "forge-std/Test.sol"; +import { Deploy } from "../script/Deploy.s.sol"; +import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; +import "../src/ScKeystore.sol"; // solhint-disable-line + +contract ScKeystoreTest is Test { + ScKeystore internal s; + DeploymentConfig internal deploymentConfig; + address internal deployer; + + function setUp() public virtual { + Deploy deployment = new Deploy(); + (s, deploymentConfig) = deployment.run(); + } + + function addUser() internal { + KeyPackage memory keyPackage = KeyPackage({ data: new bytes[](1) }); + s.addUser("0x", keyPackage); + } + + function test__userExists__returnsFalse__whenUserDoesNotExist() public view { + assert(!s.userExists(address(this))); + } + + function test__addUser__reverts__whenUserInfoIsMalformed() public { + vm.expectRevert(MalformedUserInfo.selector); + s.addUser("", KeyPackage({ data: new bytes[](0) })); + } + + function test__addUser__reverts__whenUserAlreadyExists() public { + addUser(); + vm.expectRevert(UserAlreadyExists.selector); + addUser(); + } + + function test__addUser__addsUser__whenUserInfoIsValid() public { + addUser(); + assert(s.userExists(address(this))); + } + + function test__getUser__returnsUserInfo__whenUserExists() public { + addUser(); + UserInfo memory userInfo = s.getUser(address(this)); + assert(userInfo.signaturePubKey.length == 2); + assert(userInfo.keyPackageIndices.length == 1); + } + + function test__getAllKeyPackagesForUser__returnsKeyPackages__whenUserExists() public { + addUser(); + KeyPackage[] memory keyPackages = s.getAllKeyPackagesForUser(address(this)); + assert(keyPackages.length == 1); + } +}