Skip to content

Commit f995018

Browse files
Merge pull request #84 from onchain-id/features/factory-multiple-keys
✨(IdFactory) Add method to deploy OID with management keys
2 parents a407555 + 196ff43 commit f995018

File tree

8 files changed

+121
-6
lines changed

8 files changed

+121
-6
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.0.1]
8+
9+
### Added
10+
- added method createIdentityWithManagementKeys() that allows the factory to issue identities with multiple
11+
management keys.
12+
- tests for the createIdentityWithManagementKeys() method
13+
714
## [2.0.0]
815

916
Version 2.0.0 Audited by Hacken, more details [here](https://tokeny.com/wp-content/uploads/2023/04/Tokeny_ONCHAINID_SC-Audit_Report.pdf)

contracts/factory/IIdFactory.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ interface IIdFactory {
3939
*/
4040
function createIdentity(address _wallet, string memory _salt) external returns (address);
4141

42+
/**
43+
* @dev function used to create a new Identity proxy from the factory, setting the wallet and listed keys as
44+
* MANAGEMENT keys.
45+
* @param _wallet the wallet address of the primary owner of this ONCHAINID contract
46+
* @param _salt the salt used by create2 to issue the contract
47+
* @param _managementKeys A list of keys hash (keccak256(abiEncoded())) to add as MANAGEMENT keys.
48+
* requires a new salt for each deployment
49+
* _wallet cannot be linked to another ONCHAINID
50+
* only Owner can call => Owner is supposed to be a smart contract, managing the accessibility
51+
* of the function, including calls to oracles for multichain
52+
* deployment security (avoid identity theft), defining payment requirements, etc.
53+
*/
54+
function createIdentityWithManagementKeys(
55+
address _wallet,
56+
string memory _salt,
57+
bytes32[] memory _managementKeys
58+
) external returns (address);
59+
4260
/**
4361
* @dev function used to create a new Token Identity proxy from the factory
4462
* @param _token the address of the token contract

contracts/factory/IdFactory.sol

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity 0.8.17;
33

44
import "../proxy/IdentityProxy.sol";
55
import "./IIdFactory.sol";
6+
import "../interface/IERC734.sol";
67
import "@openzeppelin/contracts/access/Ownable.sol";
78

89
contract IdFactory is IIdFactory, Ownable {
@@ -75,6 +76,47 @@ contract IdFactory is IIdFactory, Ownable {
7576
return identity;
7677
}
7778

79+
/**
80+
* @dev See {IdFactory-createIdentityWithManagementKeys}.
81+
*/
82+
function createIdentityWithManagementKeys(
83+
address _wallet,
84+
string memory _salt,
85+
bytes32[] memory _managementKeys
86+
) external onlyOwner override returns (address) {
87+
require(_wallet != address(0), "invalid argument - zero address");
88+
require(keccak256(abi.encode(_salt)) != keccak256(abi.encode("")), "invalid argument - empty string");
89+
string memory oidSalt = string.concat("OID",_salt);
90+
require (!_saltTaken[oidSalt], "salt already taken");
91+
require (_userIdentity[_wallet] == address(0), "wallet already linked to an identity");
92+
require(_managementKeys.length > 0, "invalid argument - empty list of keys");
93+
94+
address identity = _deployIdentity(oidSalt, _implementationAuthority, address(this));
95+
96+
for (uint i = 0; i < _managementKeys.length; i++) {
97+
require(
98+
_managementKeys[i] != keccak256(abi.encode(_wallet))
99+
, "invalid argument - wallet is also listed in management keys");
100+
IERC734(identity).addKey(
101+
_managementKeys[i],
102+
1,
103+
1
104+
);
105+
}
106+
107+
IERC734(identity).removeKey(
108+
keccak256(abi.encode(address(this))),
109+
1
110+
);
111+
112+
_saltTaken[oidSalt] = true;
113+
_userIdentity[_wallet] = identity;
114+
_wallets[identity].push(_wallet);
115+
emit WalletLinked(_wallet, identity);
116+
117+
return identity;
118+
}
119+
78120
/**
79121
* @dev See {IdFactory-createTokenIdentity}.
80122
*/

contracts/version/Version.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ contract Version {
1010
* @dev Returns the string of the current version.
1111
*/
1212
function version() external pure returns (string memory) {
13-
// version 2.0.0
14-
return "2.0.0";
13+
// version 2.0.1
14+
return "2.0.1";
1515
}
1616
}

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@onchain-id/solidity",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "EVM solidity smart contracts for Blockchain OnchainID identities.",
55
"files": [
66
"artifacts",

test/factory/factory.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,52 @@ describe('IdFactory', () => {
110110
});
111111
});
112112
});
113+
114+
describe('createIdentityWithManagementKeys()', () => {
115+
describe('when no management keys are provided', () => {
116+
it('should revert', async () => {
117+
const {identityFactory, deployerWallet, davidWallet} = await loadFixture(deployIdentityFixture);
118+
119+
await expect(identityFactory.connect(deployerWallet).createIdentityWithManagementKeys(davidWallet.address, 'salt1', [])).to.be.revertedWith('invalid argument - empty list of keys');
120+
});
121+
});
122+
123+
describe('when the wallet is included in the management keys listed', () => {
124+
it('should revert', async () => {
125+
const {identityFactory, deployerWallet, aliceWallet, davidWallet} = await loadFixture(deployIdentityFixture);
126+
127+
await expect(identityFactory.connect(deployerWallet).createIdentityWithManagementKeys(davidWallet.address, 'salt1', [
128+
ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address'], [aliceWallet.address])),
129+
ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address'], [davidWallet.address])),
130+
])).to.be.revertedWith('invalid argument - wallet is also listed in management keys');
131+
});
132+
});
133+
134+
describe('when other management keys are specified', () => {
135+
it('should deploy the identity proxy, set keys and wallet as management, and link wallet to identity', async () => {
136+
const {identityFactory, deployerWallet, aliceWallet, davidWallet} = await loadFixture(deployIdentityFixture);
137+
138+
const tx = await identityFactory.connect(deployerWallet).createIdentityWithManagementKeys(davidWallet.address, 'salt1', [ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address'], [aliceWallet.address]))]);
139+
140+
await expect(tx).to.emit(identityFactory, 'WalletLinked');
141+
await expect(tx).to.emit(identityFactory, 'Deployed');
142+
143+
const identity = await ethers.getContractAt('Identity', await identityFactory.getIdentity(davidWallet.address));
144+
145+
await expect(tx).to.emit(identity, 'KeyAdded').withArgs(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address'], [aliceWallet.address])), 1, 1);
146+
await expect(identity.keyHasPurpose(
147+
ethers.utils.defaultAbiCoder.encode(['address'], [identityFactory.address]),
148+
1
149+
)).to.eventually.be.false;
150+
await expect(identity.keyHasPurpose(
151+
ethers.utils.defaultAbiCoder.encode(['address'], [davidWallet.address]),
152+
1
153+
)).to.eventually.be.false;
154+
await expect(identity.keyHasPurpose(
155+
ethers.utils.defaultAbiCoder.encode(['address'], [aliceWallet.address]),
156+
1
157+
)).to.eventually.be.false;
158+
});
159+
});
160+
});
113161
});

test/identities/init.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ describe('Identity', () => {
3636
it('should return the version of the implementation', async () => {
3737
const {identityImplementation} = await loadFixture(deployIdentityFixture);
3838

39-
expect(await identityImplementation.version()).to.equal('2.0.0');
39+
expect(await identityImplementation.version()).to.equal('2.0.1');
4040
});
4141
});

0 commit comments

Comments
 (0)