Merge of 27-ens-usernames with develop
This commit is contained in:
commit
90ab6de741
|
@ -0,0 +1,63 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "./DelayedUpdatableInstanceStorage.sol";
|
||||||
|
import "./DelegatedCall.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title DelayedUpdatableInstance
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Contract that can be updated by a call from itself.
|
||||||
|
*/
|
||||||
|
contract DelayedUpdatableInstance is DelayedUpdatableInstanceStorage, DelegatedCall {
|
||||||
|
|
||||||
|
event UpdateRequested(address newKernel, uint256 activation);
|
||||||
|
event UpdateCancelled();
|
||||||
|
event UpdateConfirmed(address oldKernel, address newKernel);
|
||||||
|
|
||||||
|
function DelayedUpdatableInstance(address _kernel) public {
|
||||||
|
kernel = _kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev delegatecall everything (but declared functions) to `_target()`
|
||||||
|
* @notice Verify `kernel()` code to predict behavior
|
||||||
|
*/
|
||||||
|
function () external delegated {
|
||||||
|
//all goes to kernel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev returns kernel if kernel that is configured
|
||||||
|
* @return kernel address
|
||||||
|
*/
|
||||||
|
function targetDelegatedCall()
|
||||||
|
internal
|
||||||
|
constant
|
||||||
|
returns(address)
|
||||||
|
{
|
||||||
|
return kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestUpdatableInstance(address _kernel) external {
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
uint activation = block.timestamp + 30 days;
|
||||||
|
update = Update(_kernel, activation);
|
||||||
|
UpdateRequested(_kernel, activation);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfirmUpdatableInstance(address _kernel) external {
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
Update memory pending = update;
|
||||||
|
require(pending.kernel == _kernel);
|
||||||
|
require(pending.activation < block.timestamp);
|
||||||
|
kernel = pending.kernel;
|
||||||
|
delete update;
|
||||||
|
UpdateConfirmed(kernel, pending.kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCancelUpdatableInstance() external {
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
delete update;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "./InstanceStorage.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title InstanceStorage
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Defines kernel vars that Kernel contract share with Instance.
|
||||||
|
* Important to avoid overwriting wrong storage pointers is that
|
||||||
|
* InstanceStorage should be always the first contract at heritance.
|
||||||
|
*/
|
||||||
|
contract DelayedUpdatableInstanceStorage is InstanceStorage {
|
||||||
|
// protected zone start (InstanceStorage vars)
|
||||||
|
Update update;
|
||||||
|
|
||||||
|
struct Update {
|
||||||
|
address kernel;
|
||||||
|
uint256 activation;
|
||||||
|
}
|
||||||
|
// protected zone end
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
pragma solidity ^0.4.17;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
contract ERC725 {
|
contract ERC725 {
|
||||||
|
|
||||||
|
@ -6,19 +6,24 @@ contract ERC725 {
|
||||||
uint256 constant ACTION_KEY = 2;
|
uint256 constant ACTION_KEY = 2;
|
||||||
uint256 constant CLAIM_SIGNER_KEY = 3;
|
uint256 constant CLAIM_SIGNER_KEY = 3;
|
||||||
uint256 constant ENCRYPTION_KEY = 4;
|
uint256 constant ENCRYPTION_KEY = 4;
|
||||||
|
|
||||||
|
event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
|
||||||
|
event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
|
||||||
|
event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
|
||||||
|
event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
|
||||||
|
event Approved(uint256 indexed executionId, bool approved);
|
||||||
|
|
||||||
event KeyAdded(address indexed key, uint256 indexed keyType);
|
struct Key {
|
||||||
event KeyRemoved(address indexed key, uint256 indexed keyType);
|
uint256 purpose; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc.
|
||||||
event KeyReplaced(address indexed oldKey, address indexed newKey, uint256 indexed keyType);
|
uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc.
|
||||||
event ExecutionRequested(bytes32 indexed executionId, address indexed to, uint256 indexed value, bytes data);
|
bytes32 key;
|
||||||
event Executed(bytes32 indexed executionId, address indexed to, uint256 indexed value, bytes data);
|
}
|
||||||
event Approved(bytes32 indexed executionId, bool approved);
|
|
||||||
|
|
||||||
function getKeyType(address _key) public constant returns(uint256 keyType);
|
function getKey(bytes32 _key, uint256 _purpose) public constant returns(uint256 purpose, uint256 keyType, bytes32 key);
|
||||||
function getKeysByType(uint256 _type) public constant returns(address[]);
|
function getKeyPurpose(bytes32 _key) public constant returns(uint256[] purpose);
|
||||||
function addKey(address _key, uint256 _type) public returns (bool success);
|
function getKeysByPurpose(uint256 _purpose) public constant returns(bytes32[] keys);
|
||||||
function removeKey(address _key) public returns (bool success);
|
function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success);
|
||||||
function replaceKey(address _oldKey, address _newKey) public returns (bool success);
|
function removeKey(bytes32 _key, uint256 _purpose) public returns (bool success);
|
||||||
function execute(address _to, uint256 _value, bytes _data) public returns (bytes32 executionId);
|
function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId);
|
||||||
function approve(bytes32 _id, bool _approve) public returns (bool success);
|
function approve(uint256 _id, bool _approve) public returns (bool success);
|
||||||
}
|
}
|
|
@ -1,23 +1,23 @@
|
||||||
pragma solidity ^0.4.17;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
contract ERC735 {
|
contract ERC735 {
|
||||||
|
|
||||||
event ClaimRequested(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes signature, bytes claim, string uri);
|
event ClaimRequested(bytes32 indexed claimRequestId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
|
||||||
event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes signature, bytes claim, string uri);
|
event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
|
||||||
event ClaimRemoved(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes signature, bytes claim, string uri);
|
event ClaimRemoved(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
|
||||||
event ClaimChanged(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes signature, bytes claim, string uri);
|
event ClaimChanged(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
|
||||||
|
|
||||||
struct Claim {
|
struct Claim {
|
||||||
uint256 claimType;
|
uint256 claimType;
|
||||||
|
uint256 scheme;
|
||||||
address issuer; // msg.sender
|
address issuer; // msg.sender
|
||||||
uint256 signatureType; // The type of signature
|
bytes signature; // this.address + claimType + data
|
||||||
bytes signature; // this.address + claimType + claim
|
bytes data;
|
||||||
bytes claim;
|
|
||||||
string uri;
|
string uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, address issuer, uint256 signatureType, bytes signature, bytes claim, string uri);
|
function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
|
||||||
function getClaimsIdByType(uint256 _claimType) public constant returns(bytes32[]);
|
function getClaimIdsByType(uint256 _claimType) public constant returns(bytes32[] claimIds);
|
||||||
function addClaim(uint256 _claimType, address issuer, uint256 signatureType, bytes _signature, bytes _claim, string _uri) public returns (bytes32 claimId);
|
function addClaim(uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (bytes32 claimRequestId);
|
||||||
function removeClaim(bytes32 _claimId) public returns (bool success);
|
function removeClaim(bytes32 _claimId) public returns (bool success);
|
||||||
}
|
}
|
|
@ -1,214 +1,621 @@
|
||||||
pragma solidity ^0.4.17;
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
import "./ERC725.sol";
|
import "./ERC725.sol";
|
||||||
import "./ERC735.sol";
|
import "./ERC735.sol";
|
||||||
|
|
||||||
contract Identity is ERC725, ERC735 {
|
|
||||||
|
|
||||||
mapping (address => uint256) keys;
|
|
||||||
mapping (bytes32 => Claim) claims;
|
|
||||||
mapping (uint256 => address[]) keysByType;
|
|
||||||
mapping (uint256 => bytes32[]) claimsByType;
|
|
||||||
mapping (bytes32 => uint256) indexes;
|
|
||||||
mapping (bytes32 => Transaction) txx;
|
|
||||||
|
|
||||||
uint nonce = 0;
|
|
||||||
|
|
||||||
struct Transaction {
|
|
||||||
address to;
|
|
||||||
uint value;
|
|
||||||
bytes data;
|
|
||||||
uint nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
modifier managerOnly {
|
contract Identity is ERC725, ERC735 {
|
||||||
require(keys[msg.sender] == MANAGEMENT_KEY);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier managerOrSelf {
|
mapping (bytes32 => Key) keys;
|
||||||
require(keys[msg.sender] == MANAGEMENT_KEY || msg.sender == address(this));
|
mapping (bytes32 => Claim) claims;
|
||||||
_;
|
mapping (uint256 => bytes32[]) keysByPurpose;
|
||||||
}
|
mapping (uint256 => bytes32[]) claimsByType;
|
||||||
|
mapping (bytes32 => uint256) indexes;
|
||||||
|
mapping (uint => Transaction) txx;
|
||||||
|
mapping (uint256 => uint256) minimumApprovalsByKeyPurpose;
|
||||||
|
bytes32[] pendingTransactions;
|
||||||
|
uint nonce = 0;
|
||||||
|
address recoveryContract;
|
||||||
|
address recoveryManager;
|
||||||
|
|
||||||
modifier actorOnly {
|
struct Transaction {
|
||||||
require(keys[msg.sender] == ACTION_KEY);
|
address to;
|
||||||
_;
|
uint value;
|
||||||
}
|
bytes data;
|
||||||
|
uint nonce;
|
||||||
|
uint approverCount;
|
||||||
|
mapping(bytes32 => bool) approvals;
|
||||||
|
}
|
||||||
|
|
||||||
modifier claimSignerOnly {
|
modifier managerOnly {
|
||||||
require(keys[msg.sender] == CLAIM_SIGNER_KEY);
|
require(
|
||||||
_;
|
isKeyType(bytes32(msg.sender), MANAGEMENT_KEY)
|
||||||
}
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier selfOnly {
|
||||||
|
require(
|
||||||
|
msg.sender == address(this)
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
function Identity() public {
|
modifier recoveryOnly {
|
||||||
_addKey(msg.sender, MANAGEMENT_KEY);
|
require(
|
||||||
}
|
(recoveryContract != address(0) && msg.sender == address(recoveryContract))
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier actorOnly(bytes32 _key) {
|
||||||
|
require(isKeyType(_key, ACTION_KEY));
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier managerOrActor(bytes32 _key) {
|
||||||
|
require(
|
||||||
|
isKeyType(bytes32(msg.sender), MANAGEMENT_KEY) ||
|
||||||
|
isKeyType(bytes32(msg.sender), ACTION_KEY)
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier validECDSAKey (
|
||||||
|
bytes32 _key,
|
||||||
|
bytes32 _signHash,
|
||||||
|
uint8 _v,
|
||||||
|
bytes32 _r,
|
||||||
|
bytes32 _s
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(address(_key) == ecrecover(
|
||||||
|
keccak256("\x19Ethereum Signed Message:\n32", _signHash),
|
||||||
|
_v,
|
||||||
|
_r,
|
||||||
|
_s
|
||||||
|
));
|
||||||
|
require(keys[_key].purpose != 0);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
function addKey(address _key, uint256 _type) public managerOrSelf returns (bool success) {
|
function Identity() public {
|
||||||
_addKey(_key, _type);
|
_constructIdentity(msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function removeKey(address _key) public managerOrSelf returns (bool success) {
|
|
||||||
_removeKey(_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function replaceKey(address _oldKey, address _newKey) public managerOrSelf returns (bool success) {
|
|
||||||
_addKey(_newKey, getKeyType(_oldKey));
|
|
||||||
_removeKey(_oldKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function execute(
|
|
||||||
address _to,
|
|
||||||
uint256 _value,
|
|
||||||
bytes _data
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (bytes32 executionId)
|
|
||||||
{
|
|
||||||
uint256 senderKey = keys[msg.sender];
|
|
||||||
require(senderKey == MANAGEMENT_KEY || senderKey == ACTION_KEY);
|
|
||||||
executionId = keccak256(_to, _value, _data, nonce);
|
|
||||||
ExecutionRequested(executionId, _to, _value, _data);
|
|
||||||
txx[executionId] = Transaction (
|
|
||||||
{
|
|
||||||
to: _to,
|
|
||||||
value: _value,
|
|
||||||
data: _data,
|
|
||||||
nonce: nonce
|
|
||||||
});
|
|
||||||
if (senderKey == MANAGEMENT_KEY) {
|
|
||||||
approve(executionId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function approve(
|
|
||||||
bytes32 _id,
|
|
||||||
bool _approve
|
|
||||||
)
|
|
||||||
public
|
|
||||||
managerOnly
|
|
||||||
returns (bool success)
|
|
||||||
{
|
|
||||||
require(txx[_id].nonce == nonce);
|
|
||||||
nonce++;
|
|
||||||
if (_approve) {
|
|
||||||
success = txx[_id].to.call.value(txx[_id].value)(txx[_id].data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addClaim(
|
|
||||||
uint256 _claimType,
|
|
||||||
address _issuer,
|
|
||||||
uint256 _signatureType,
|
|
||||||
bytes _signature,
|
|
||||||
bytes _claim,
|
|
||||||
string _uri
|
|
||||||
)
|
|
||||||
public
|
|
||||||
claimSignerOnly
|
|
||||||
returns (bytes32 claimId)
|
|
||||||
{
|
|
||||||
claimId = keccak256(_issuer, _claimType);
|
|
||||||
claims[claimId] = Claim(
|
|
||||||
{
|
|
||||||
claimType: _claimType,
|
|
||||||
issuer: _issuer,
|
|
||||||
signatureType: _signatureType,
|
|
||||||
signature: _signature,
|
|
||||||
claim: _claim,
|
|
||||||
uri: _uri
|
|
||||||
}
|
|
||||||
);
|
|
||||||
indexes[keccak256(_issuer, _claimType)] = claimsByType[_claimType].length;
|
|
||||||
claimsByType[_claimType].push(claimId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function removeClaim(bytes32 _claimId) public returns (bool success) {
|
|
||||||
Claim memory c = claims[_claimId];
|
|
||||||
require(msg.sender == c.issuer || keys[msg.sender] == MANAGEMENT_KEY || msg.sender == address(this));
|
|
||||||
uint claimIdTypePos = indexes[_claimId];
|
|
||||||
delete indexes[_claimId];
|
|
||||||
bytes32[] storage claimsTypeArr = claimsByType[c.claimType];
|
|
||||||
bytes32 replacer = claimsTypeArr[claimsTypeArr.length-1];
|
|
||||||
claimsTypeArr[claimIdTypePos] = replacer;
|
|
||||||
indexes[replacer] = claimIdTypePos;
|
|
||||||
delete claims[_claimId];
|
|
||||||
claimsTypeArr.length--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _addKey(address _key, uint256 _type) private {
|
|
||||||
require(keys[_key] == 0);
|
|
||||||
KeyAdded(_key, _type);
|
|
||||||
keys[_key] = _type;
|
|
||||||
indexes[keccak256(_key, _type)] = keysByType[_type].length;
|
|
||||||
keysByType[_type].push(_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _removeKey(address _key) private {
|
|
||||||
uint256 kType = keys[_key];
|
|
||||||
KeyRemoved(_key, kType);
|
|
||||||
address[] storage keyArr = keysByType[kType];
|
|
||||||
if (msg.sender != address(this) && kType == MANAGEMENT_KEY && keyArr.length == 1) {
|
|
||||||
revert();
|
|
||||||
}
|
|
||||||
bytes32 oldIndex = keccak256(_key, kType);
|
|
||||||
uint index = indexes[oldIndex];
|
|
||||||
delete indexes[oldIndex];
|
|
||||||
address replacer = keyArr[keyArr.length-1];
|
|
||||||
keyArr[index] = replacer;
|
|
||||||
indexes[keccak256(replacer, keys[replacer])] = index;
|
|
||||||
keyArr.length--;
|
|
||||||
delete keys[_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getKeyType(address _key) public constant returns(uint256 keyType) {
|
|
||||||
return keys[_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getKeysByType(uint256 _type) public constant returns(address[]) {
|
|
||||||
return keysByType[_type];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getClaim(
|
|
||||||
bytes32 _claimId
|
|
||||||
)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns
|
|
||||||
(uint256 claimType,
|
|
||||||
address issuer,
|
|
||||||
uint256 signatureType,
|
|
||||||
bytes signature,
|
|
||||||
bytes claim,
|
|
||||||
string uri)
|
|
||||||
{
|
|
||||||
Claim memory _claim = claims[_claimId];
|
|
||||||
return (_claim.claimType, _claim.issuer, _claim.signatureType, _claim.signature, _claim.claim, _claim.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getClaimsIdByType(uint256 _claimType) public constant returns(bytes32[]) {
|
|
||||||
return claimsByType[_claimType];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function ()
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function managerReset(address _newKey)
|
||||||
|
public
|
||||||
|
recoveryOnly
|
||||||
|
{
|
||||||
|
recoveryManager = _newKey;
|
||||||
|
_addKey(bytes32(recoveryManager), MANAGEMENT_KEY, 0);
|
||||||
|
minimumApprovalsByKeyPurpose[MANAGEMENT_KEY] = keysByPurpose[MANAGEMENT_KEY].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processManagerReset(uint256 limit)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
require(recoveryManager != address(0));
|
||||||
|
bytes32 newKey = bytes32(recoveryManager);
|
||||||
|
bytes32[] memory managers = keysByPurpose[MANAGEMENT_KEY];
|
||||||
|
uint256 totalManagers = managers.length;
|
||||||
|
|
||||||
|
if (limit == 0) {
|
||||||
|
limit = totalManagers;
|
||||||
|
}
|
||||||
|
|
||||||
|
minimumApprovalsByKeyPurpose[MANAGEMENT_KEY] = totalManagers - limit + 1;
|
||||||
|
for (uint256 i = 0; i < limit; i++) {
|
||||||
|
bytes32 manager = managers[i];
|
||||||
|
if (manager != newKey) {
|
||||||
|
_removeKey(manager, MANAGEMENT_KEY);
|
||||||
|
totalManagers--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalManagers == 1) {
|
||||||
|
recoveryManager = address(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose,
|
||||||
|
uint256 _type
|
||||||
|
)
|
||||||
|
public
|
||||||
|
selfOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
_addKey(_key, _purpose, _type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceKey(
|
||||||
|
bytes32 _oldKey,
|
||||||
|
bytes32 _newKey,
|
||||||
|
uint256 _newType
|
||||||
|
)
|
||||||
|
public
|
||||||
|
selfOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
uint256 purpose = keys[_oldKey].purpose;
|
||||||
|
_addKey(_newKey, purpose, _newType);
|
||||||
|
_removeKey(_oldKey, purpose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose
|
||||||
|
)
|
||||||
|
public
|
||||||
|
selfOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
_removeKey(_key, _purpose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
public
|
||||||
|
managerOrActor(bytes32(msg.sender))
|
||||||
|
returns (uint256 executionId)
|
||||||
|
{
|
||||||
|
executionId = _execute(_to, _value, _data);
|
||||||
|
approve(executionId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function approve(uint256 _id, bool _approval)
|
||||||
|
public
|
||||||
|
managerOrActor(bytes32(msg.sender))
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
return _approve(bytes32(msg.sender), _id, _approval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMinimumApprovalsByKeyType(
|
||||||
|
uint256 _purpose,
|
||||||
|
uint256 _minimumApprovals
|
||||||
|
)
|
||||||
|
public
|
||||||
|
selfOnly
|
||||||
|
{
|
||||||
|
require(_minimumApprovals > 0);
|
||||||
|
require(_minimumApprovals <= keysByPurpose[_purpose].length);
|
||||||
|
minimumApprovalsByKeyPurpose[_purpose] = _minimumApprovals;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addClaim(
|
||||||
|
uint256 _claimType,
|
||||||
|
uint256 _scheme,
|
||||||
|
address _issuer,
|
||||||
|
bytes _signature,
|
||||||
|
bytes _data,
|
||||||
|
string _uri
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bytes32 claimHash)
|
||||||
|
{
|
||||||
|
claimHash = keccak256(_issuer, _claimType);
|
||||||
|
if (msg.sender == address(this)) {
|
||||||
|
if (claims[claimHash].claimType > 0) {
|
||||||
|
_modifyClaim(claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri);
|
||||||
|
} else {
|
||||||
|
_includeClaim(claimHash, _claimType, _scheme, _issuer, _signature, _data, _uri);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
require(_issuer == msg.sender);
|
||||||
|
require(isKeyType(bytes32(msg.sender), CLAIM_SIGNER_KEY));
|
||||||
|
_execute(address(this), 0, msg.data);
|
||||||
|
ClaimRequested(
|
||||||
|
claimHash,
|
||||||
|
_claimType,
|
||||||
|
_scheme,
|
||||||
|
_issuer,
|
||||||
|
_signature,
|
||||||
|
_data,
|
||||||
|
_uri
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClaim(bytes32 _claimId)
|
||||||
|
public
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
Claim memory c = claims[_claimId];
|
||||||
|
|
||||||
|
require(
|
||||||
|
msg.sender == c.issuer ||
|
||||||
|
msg.sender == address(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
// MUST only be done by the issuer of the claim, or KEYS OF PURPOSE 1, or the identity itself.
|
||||||
|
// TODO If its the identity itself, the approval process will determine its approval.
|
||||||
|
|
||||||
|
uint claimIdTypePos = indexes[_claimId];
|
||||||
|
delete indexes[_claimId];
|
||||||
|
bytes32[] storage claimsTypeArr = claimsByType[c.claimType];
|
||||||
|
bytes32 replacer = claimsTypeArr[claimsTypeArr.length-1];
|
||||||
|
claimsTypeArr[claimIdTypePos] = replacer;
|
||||||
|
indexes[replacer] = claimIdTypePos;
|
||||||
|
delete claims[_claimId];
|
||||||
|
claimsTypeArr.length--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose
|
||||||
|
)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns(uint256 purpose, uint256 keyType, bytes32 key)
|
||||||
|
{
|
||||||
|
Key storage myKey = keys[keccak256(_key, _purpose)];
|
||||||
|
return (myKey.purpose, myKey.keyType, myKey.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyType(bytes32 _key, uint256 _type)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return keys[keccak256(_key, _type)].purpose == _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyPurpose(bytes32 _key)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns(uint256[] purpose)
|
||||||
|
{
|
||||||
|
|
||||||
|
uint256[] memory purposeHolder = new uint256[](4);
|
||||||
|
uint8 counter = 0;
|
||||||
|
|
||||||
|
if (isKeyType(_key, MANAGEMENT_KEY)) {
|
||||||
|
purposeHolder[counter] = MANAGEMENT_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyType(_key, ACTION_KEY)) {
|
||||||
|
purposeHolder[counter] = ACTION_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyType(_key, CLAIM_SIGNER_KEY)) {
|
||||||
|
purposeHolder[counter] = CLAIM_SIGNER_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyType(_key, ENCRYPTION_KEY)) {
|
||||||
|
purposeHolder[counter] = ENCRYPTION_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256[] memory result = new uint256[](counter);
|
||||||
|
for (uint8 i = 0; i < counter; i++) {
|
||||||
|
result[i] = purposeHolder[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeysByPurpose(uint256 _purpose)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns(bytes32[])
|
||||||
|
{
|
||||||
|
return keysByPurpose[_purpose];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClaim(bytes32 _claimId)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns(
|
||||||
|
uint256 claimType,
|
||||||
|
uint256 scheme,
|
||||||
|
address issuer,
|
||||||
|
bytes signature,
|
||||||
|
bytes data,
|
||||||
|
string uri
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Claim memory _claim = claims[_claimId];
|
||||||
|
return (_claim.claimType, _claim.scheme, _claim.issuer, _claim.signature, _claim.data, _claim.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClaimIdsByType(uint256 _claimType)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns(bytes32[] claimIds)
|
||||||
|
{
|
||||||
|
return claimsByType[_claimType];
|
||||||
|
}
|
||||||
|
|
||||||
|
function approveECDSA(
|
||||||
|
uint256 _id,
|
||||||
|
bool _approval,
|
||||||
|
bytes32 _key,
|
||||||
|
uint8 _v,
|
||||||
|
bytes32 _r,
|
||||||
|
bytes32 _s
|
||||||
|
)
|
||||||
|
public
|
||||||
|
validECDSAKey(
|
||||||
|
_key,
|
||||||
|
keccak256(
|
||||||
|
address(this),
|
||||||
|
bytes4(keccak256("approve(uint256,bool)")),
|
||||||
|
_id,
|
||||||
|
_approval
|
||||||
|
),
|
||||||
|
_v,
|
||||||
|
_r,
|
||||||
|
_s
|
||||||
|
)
|
||||||
|
managerOrActor(_key)
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
return _approve(_key, _id, _approval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeECDSA(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data,
|
||||||
|
uint _nonce,
|
||||||
|
bytes32 _key,
|
||||||
|
uint8 _v,
|
||||||
|
bytes32 _r,
|
||||||
|
bytes32 _s
|
||||||
|
)
|
||||||
|
public
|
||||||
|
validECDSAKey(
|
||||||
|
_key,
|
||||||
|
keccak256(
|
||||||
|
address(this),
|
||||||
|
bytes4(
|
||||||
|
keccak256("execute(address,uint256,bytes)")),
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
_data,
|
||||||
|
_nonce
|
||||||
|
),
|
||||||
|
_v,
|
||||||
|
_r,
|
||||||
|
_s
|
||||||
|
)
|
||||||
|
managerOrActor(_key)
|
||||||
|
returns (uint256 executionId)
|
||||||
|
{
|
||||||
|
executionId = _execute(_to, _value, _data);
|
||||||
|
_approve(_key, executionId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupRecovery(address _recoveryContract)
|
||||||
|
public
|
||||||
|
selfOnly
|
||||||
|
{
|
||||||
|
require(recoveryContract == address(0));
|
||||||
|
recoveryContract = _recoveryContract;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _constructIdentity(address _manager)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
require(minimumApprovalsByKeyPurpose[MANAGEMENT_KEY] == 0);
|
||||||
|
_addKey(bytes32(_manager), MANAGEMENT_KEY, 0);
|
||||||
|
|
||||||
|
minimumApprovalsByKeyPurpose[MANAGEMENT_KEY] = 1;
|
||||||
|
minimumApprovalsByKeyPurpose[ACTION_KEY] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 executionId)
|
||||||
|
{
|
||||||
|
executionId = nonce;
|
||||||
|
ExecutionRequested(executionId, _to, _value, _data);
|
||||||
|
txx[executionId] = Transaction(
|
||||||
|
{
|
||||||
|
to: _to,
|
||||||
|
value: _value,
|
||||||
|
data: _data,
|
||||||
|
nonce: nonce,
|
||||||
|
approverCount: 0
|
||||||
|
});
|
||||||
|
nonce++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _approve(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _id,
|
||||||
|
bool _approval
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns(bool success)
|
||||||
|
{
|
||||||
|
|
||||||
|
Transaction storage trx = txx[_id];
|
||||||
|
|
||||||
|
uint256 approvalCount;
|
||||||
|
uint256 requiredKeyPurpose;
|
||||||
|
|
||||||
|
Approved(_id, _approval);
|
||||||
|
|
||||||
|
if (trx.to == address(this)) {
|
||||||
|
require(isKeyType(_key, MANAGEMENT_KEY));
|
||||||
|
bytes32 managerKeyHash = keccak256(_key, MANAGEMENT_KEY);
|
||||||
|
requiredKeyPurpose = MANAGEMENT_KEY;
|
||||||
|
approvalCount = _calculateApprovals(managerKeyHash, _approval, trx);
|
||||||
|
} else {
|
||||||
|
require(isKeyType(_key, ACTION_KEY));
|
||||||
|
bytes32 actorKeyHash = keccak256(_key, ACTION_KEY);
|
||||||
|
requiredKeyPurpose = ACTION_KEY;
|
||||||
|
approvalCount = _calculateApprovals(actorKeyHash, _approval, trx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (approvalCount >= minimumApprovalsByKeyPurpose[requiredKeyPurpose]) {
|
||||||
|
Executed(_id, trx.to, trx.value, trx.data);
|
||||||
|
success = trx.to.call.value(trx.value)(trx.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _addKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose,
|
||||||
|
uint256 _type
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
bytes32 keyHash = keccak256(_key, _purpose);
|
||||||
|
|
||||||
|
require(keys[keyHash].purpose == 0);
|
||||||
|
require(
|
||||||
|
_purpose == MANAGEMENT_KEY ||
|
||||||
|
_purpose == ACTION_KEY ||
|
||||||
|
_purpose == CLAIM_SIGNER_KEY ||
|
||||||
|
_purpose == ENCRYPTION_KEY
|
||||||
|
);
|
||||||
|
KeyAdded(_key, _purpose, _type);
|
||||||
|
keys[keyHash] = Key(_purpose, _type, _key);
|
||||||
|
indexes[keyHash] = keysByPurpose[_purpose].push(_key) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
bytes32 keyHash = keccak256(_key, _purpose);
|
||||||
|
Key storage myKey = keys[keyHash];
|
||||||
|
KeyRemoved(myKey.key, myKey.purpose, myKey.keyType);
|
||||||
|
|
||||||
|
uint index = indexes[keyHash];
|
||||||
|
delete indexes[keyHash];
|
||||||
|
bytes32 replacer = keysByPurpose[_purpose][keysByPurpose[_purpose].length - 1];
|
||||||
|
keysByPurpose[_purpose][index] = replacer;
|
||||||
|
indexes[keccak256(replacer, _purpose)] = index;
|
||||||
|
keysByPurpose[_purpose].length--;
|
||||||
|
|
||||||
|
if (_purpose == MANAGEMENT_KEY) {
|
||||||
|
require(
|
||||||
|
keysByPurpose[MANAGEMENT_KEY].length >= 1 &&
|
||||||
|
keysByPurpose[MANAGEMENT_KEY].length >= minimumApprovalsByKeyPurpose[MANAGEMENT_KEY]
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
delete keys[keyHash];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _calculateApprovals(
|
||||||
|
bytes32 _keyHash,
|
||||||
|
bool _approval,
|
||||||
|
Transaction storage trx
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 approvalCount)
|
||||||
|
{
|
||||||
|
require(trx.approvals[_keyHash] != _approval);
|
||||||
|
|
||||||
|
trx.approvals[_keyHash] = _approval;
|
||||||
|
if (_approval) {
|
||||||
|
trx.approverCount++;
|
||||||
|
} else {
|
||||||
|
trx.approverCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trx.approverCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _includeClaim(
|
||||||
|
bytes32 _claimHash,
|
||||||
|
uint256 _claimType,
|
||||||
|
uint256 _scheme,
|
||||||
|
address _issuer,
|
||||||
|
bytes _signature,
|
||||||
|
bytes _data,
|
||||||
|
string _uri
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
claims[_claimHash] = Claim(
|
||||||
|
{
|
||||||
|
claimType: _claimType,
|
||||||
|
scheme: _scheme,
|
||||||
|
issuer: _issuer,
|
||||||
|
signature: _signature,
|
||||||
|
data: _data,
|
||||||
|
uri: _uri
|
||||||
|
}
|
||||||
|
);
|
||||||
|
indexes[_claimHash] = claimsByType[_claimType].length;
|
||||||
|
claimsByType[_claimType].push(_claimHash);
|
||||||
|
ClaimAdded(
|
||||||
|
_claimHash,
|
||||||
|
_claimType,
|
||||||
|
_scheme,
|
||||||
|
_issuer,
|
||||||
|
_signature,
|
||||||
|
_data,
|
||||||
|
_uri
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _modifyClaim(
|
||||||
|
bytes32 _claimHash,
|
||||||
|
uint256 _claimType,
|
||||||
|
uint256 _scheme,
|
||||||
|
address _issuer,
|
||||||
|
bytes _signature,
|
||||||
|
bytes _data,
|
||||||
|
string _uri
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
require(msg.sender == _issuer);
|
||||||
|
ClaimChanged(
|
||||||
|
_claimHash,
|
||||||
|
_claimType,
|
||||||
|
_scheme,
|
||||||
|
_issuer,
|
||||||
|
_signature,
|
||||||
|
_data,
|
||||||
|
_uri
|
||||||
|
);
|
||||||
|
claims[_claimHash] = Claim({
|
||||||
|
claimType: _claimType,
|
||||||
|
scheme: _scheme,
|
||||||
|
issuer: _issuer,
|
||||||
|
signature: _signature,
|
||||||
|
data: _data,
|
||||||
|
uri: _uri
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "../deploy/Factory.sol";
|
||||||
|
import "../deploy/DelayedUpdatableInstance.sol";
|
||||||
|
import "./IdentityKernel.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract IdentityFactory is Factory {
|
||||||
|
|
||||||
|
event IdentityCreated(address instance);
|
||||||
|
|
||||||
|
function IdentityFactory(bytes _infohash)
|
||||||
|
public
|
||||||
|
Factory(new IdentityKernel(), _infohash)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIdentity()
|
||||||
|
external
|
||||||
|
{
|
||||||
|
createIdentity(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIdentity(address _idOwner)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
IdentityKernel instance = IdentityKernel(new DelayedUpdatableInstance(address(latestKernel)));
|
||||||
|
instance.initIdentity(_idOwner);
|
||||||
|
IdentityCreated(address(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "../deploy/DelayedUpdatableInstanceStorage.sol";
|
||||||
|
import "./Identity.sol";
|
||||||
|
|
||||||
|
contract IdentityKernel is DelayedUpdatableInstanceStorage, Identity {
|
||||||
|
|
||||||
|
function initIdentity(address _caller) external {
|
||||||
|
_constructIdentity(_caller);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
|
||||||
|
contract TestContract {
|
||||||
|
|
||||||
|
event TestFunctionExecuted();
|
||||||
|
|
||||||
|
function test() public {
|
||||||
|
TestFunctionExecuted();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Helper function to be used in unit testing due to error in web3
|
||||||
|
web3.utils.soliditySha3([1, 2, 3])
|
||||||
|
Error: Autodetection of array types is not supported.
|
||||||
|
at _processSoliditySha3Args (node_modules/web3-utils/src/soliditySha3.js:176:15)
|
||||||
|
*/
|
||||||
|
function hash(
|
||||||
|
address identity,
|
||||||
|
bytes32 _revealedSecret,
|
||||||
|
address _dest,
|
||||||
|
bytes _data,
|
||||||
|
bytes32 _newSecret,
|
||||||
|
bytes32[] _newFriendsHashes)
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns(bytes32)
|
||||||
|
{
|
||||||
|
return keccak256(identity, _revealedSecret, _dest, _data, _newSecret, _newFriendsHashes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "../identity/IdentityKernel.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract UpdatedIdentityKernel is IdentityKernel {
|
||||||
|
|
||||||
|
event TestFunctionExecuted(uint256 minApprovalsByManagementKeys);
|
||||||
|
|
||||||
|
function test() public {
|
||||||
|
TestFunctionExecuted(minimumApprovalsByKeyPurpose[MANAGEMENT_KEY]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
const TestUtils = require("../utils/testUtils.js")
|
||||||
|
const idUtils = require("../utils/identityUtils.js")
|
||||||
|
const web3EthAbi = require("web3-eth-abi");
|
||||||
|
|
||||||
|
const Identity = artifacts.require("./identity/Identity.sol");
|
||||||
|
const IdentityFactory = artifacts.require("./identity/IdentityFactory.sol");
|
||||||
|
const UpdatableInstance = artifacts.require('./deploy/UpdatableInstance.sol');
|
||||||
|
const UpdatedIdentityKernel = artifacts.require("./tests/UpdatedIdentityKernel.sol");
|
||||||
|
|
||||||
|
contract('IdentityFactory', function(accounts) {
|
||||||
|
|
||||||
|
let identityFactory;
|
||||||
|
let identity;
|
||||||
|
let updatedIdentity;
|
||||||
|
let updatedIdentityKernel;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
identityFactory = await IdentityFactory.new("0xaaa", {from: accounts[0]});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("IdentityFactory()", () => {
|
||||||
|
it("Creates a new identity", async () => {
|
||||||
|
let tx = await identityFactory.createIdentity({from: accounts[0]});
|
||||||
|
const logEntry = tx.logs[0];
|
||||||
|
assert.strictEqual(logEntry.event, "IdentityCreated");
|
||||||
|
|
||||||
|
identity = await Identity.at(logEntry.args.instance, {from: accounts[0]})
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Registers a updated identity contract", async() => {
|
||||||
|
const infoHash = "0xbbb";
|
||||||
|
updatedIdentityKernel = await UpdatedIdentityKernel.new({from: accounts[0]});
|
||||||
|
await identityFactory.setKernel(updatedIdentityKernel.address, infoHash);
|
||||||
|
|
||||||
|
const newKernel = await TestUtils.listenForEvent(identityFactory.NewKernel());
|
||||||
|
assert(newKernel.infohash, infoHash, "Infohash is not correct");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Creates a new identity using latest version", async() => {
|
||||||
|
let tx = await identityFactory.createIdentity({from: accounts[0]});
|
||||||
|
const logEntry = tx.logs[0];
|
||||||
|
assert.strictEqual(logEntry.event, "IdentityCreated");
|
||||||
|
|
||||||
|
updatedIdentity = await UpdatedIdentityKernel.at(logEntry.args.instance, {from: accounts[0]})
|
||||||
|
tx = await updatedIdentity.test({from: accounts[0]});
|
||||||
|
assert.strictEqual(tx.logs[0].event, "TestFunctionExecuted");
|
||||||
|
|
||||||
|
// Test if it still executes identity functions as expected
|
||||||
|
let baseIdentity = await Identity.at(updatedIdentity.address, {from: accounts[0]})
|
||||||
|
assert.equal(
|
||||||
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])),
|
||||||
|
1,
|
||||||
|
identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Updates an identity to the latest version", async() => {
|
||||||
|
let tx1 = await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.updateRequestUpdatableInstance(updatedIdentityKernel.address),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(tx1.logs[tx1.logs.length - 1].event, "Executed");
|
||||||
|
|
||||||
|
// Updating EVM timestamp to test delay
|
||||||
|
const plus31days = 60 * 60 * 24 * 31;
|
||||||
|
|
||||||
|
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [plus31days], id: 0});
|
||||||
|
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 0})
|
||||||
|
|
||||||
|
// Confirm update
|
||||||
|
let tx2 = await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.updateConfirmUpdatableInstance(updatedIdentityKernel.address),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(tx2.logs[tx2.logs.length - 1].event, "Executed");
|
||||||
|
|
||||||
|
// Calling function available in updated identity kernel
|
||||||
|
let updatedIdentity1 = await UpdatedIdentityKernel.at(identity.address, {from: accounts[0]})
|
||||||
|
let tx3 = await updatedIdentity1.test({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.strictEqual(tx3.logs[tx3.logs.length - 1].event, "TestFunctionExecuted");
|
||||||
|
assert.equal(
|
||||||
|
tx3.logs[tx3.logs.length - 1].args.minApprovalsByManagementKeys.toString(10),
|
||||||
|
1,
|
||||||
|
identity.address + " wasn't updated to last version");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
457
test/identity.js
457
test/identity.js
|
@ -1,169 +1,265 @@
|
||||||
const TestUtils = require("./TestUtils.js")
|
const TestUtils = require("../utils/testUtils.js");
|
||||||
|
const web3EthAbi = require("web3-eth-abi");
|
||||||
|
const idUtils = require('../utils/identityUtils.js');
|
||||||
|
|
||||||
const Identity = artifacts.require("./identity/Identity.sol");
|
const Identity = artifacts.require("./identity/Identity.sol");
|
||||||
|
const TestContract = artifacts.require("./test/TestContract.sol");
|
||||||
|
|
||||||
contract('Identity', function(accounts) {
|
contract('Identity', function(accounts) {
|
||||||
|
|
||||||
let identity;
|
let identity;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
identity = await Identity.new({from: accounts[0]})
|
identity = await Identity.new({from: accounts[0]})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
describe("Identity()", () => {
|
describe("Identity()", () => {
|
||||||
|
|
||||||
it("initialize with msg.sender as management key", async () => {
|
it("initialize with msg.sender as management key", async () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[0]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])),
|
||||||
1,
|
idUtils.purposes.MANAGEMENT,
|
||||||
identity.address+".getKeyType("+accounts[0]+") is not MANAGEMENT_KEY")
|
identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("addKey(address _key, uint256 _type)", () => {
|
describe("addKey(address _key, uint256 _type)", () => {
|
||||||
|
|
||||||
it("MANAGEMENT_KEY add a new address as ACTION_KEY", async () => {
|
it("MANAGEMENT_KEY add a new address as ACTION_KEY", async () => {
|
||||||
await identity.addKey(accounts[1], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
assert.equal(
|
identity.address,
|
||||||
await identity.getKeyType(accounts[1]),
|
0,
|
||||||
2,
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not ACTION_KEY")
|
{from: accounts[0]}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
|
idUtils.purposes.ACTION,
|
||||||
|
identity.address+".getKeyPurpose("+accounts[1]+") is not ACTION_KEY")
|
||||||
|
});
|
||||||
|
|
||||||
it("should not add key by non manager", async () => {
|
it("should not add key by non manager", async () => {
|
||||||
try {
|
try {
|
||||||
await identity.addKey(accounts[1], 1, {from: accounts[2]})
|
await identity.execute(
|
||||||
}catch(e){
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[2]}
|
||||||
|
);
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
0,
|
idUtils.purposes.NONE,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not add key type 1 by actor", async () => {
|
it("should not add key type 1 by actor", async () => {
|
||||||
await identity.addKey(accounts[2], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[2], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await identity.addKey(accounts[1], 1, {from: accounts[2]})
|
await identity.execute(
|
||||||
} catch(e){
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[2]}
|
||||||
|
);
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
0,
|
idUtils.purposes.NONE,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
it("fire KeyAdded(address indexed key, uint256 indexed type)", async () => {
|
it("fire KeyAdded(address indexed key, uint256 indexed type)", async () => {
|
||||||
identity.addKey(accounts[1], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
const keyAdded = await TestUtils.listenForEvent(identity.KeyAdded())
|
const keyAdded = await TestUtils.listenForEvent(identity.KeyAdded())
|
||||||
assert(keyAdded.key, accounts[1], "Key is not correct")
|
assert(keyAdded.key, TestUtils.addressToBytes32(accounts[1]), "Key is not correct")
|
||||||
assert(keyAdded.keyType, 2, "Type is not correct")
|
assert(keyAdded.keyType, idUtils.types.ADDRESS, "Type is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("removeKey(address _key, uint256 _type)", () => {
|
describe("removeKey(address _key, uint256 _type)", () => {
|
||||||
|
it("MANAGEMENT_KEY should remove a key", async () => {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
it("MANAGEMENT_KEY should removes a key", async () => {
|
await identity.execute(
|
||||||
await identity.addKey(accounts[1], 1, {from: accounts[0]})
|
identity.address,
|
||||||
await identity.removeKey(accounts[0], {from: accounts[1]})
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.MANAGEMENT),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[0]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
0,
|
idUtils.purposes.NONE,
|
||||||
identity.address+".getKeyType("+accounts[0]+") is not 0")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not 0")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("other key should not removes a key", async () => {
|
it("other key should not remove a key", async () => {
|
||||||
await identity.addKey(accounts[1], 1, {from: accounts[0]})
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await identity.removeKey(accounts[1], {from: accounts[2]})
|
await identity.execute(
|
||||||
}catch (e) {
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.MANAGEMENT),
|
||||||
|
{from: accounts[2]}
|
||||||
|
);
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
1,
|
idUtils.purposes.MANAGEMENT,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not 0")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not 0")
|
||||||
});
|
});
|
||||||
|
|
||||||
it("actor key should not remove key", async () => {
|
it("actor key should not remove key", async () => {
|
||||||
await identity.addKey(accounts[1], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
await identity.addKey(accounts[2], 2, {from: accounts[0]})
|
identity.address,
|
||||||
try {
|
0,
|
||||||
await identity.removeKey(accounts[1], {from: accounts[2]})
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
}catch (e) {
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[2], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.ACTION),
|
||||||
|
{from: accounts[2]}
|
||||||
|
);
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
2,
|
idUtils.purposes.ACTION,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not 0")
|
identity.address+".getKeyType("+accounts[1]+") is not 0")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("MANAGEMENT_KEY should not remove itself MANAGEMENT_KEY when there is no other MANAGEMENT_KEY", async () => {
|
it("MANAGEMENT_KEY should not remove itself MANAGEMENT_KEY when there is no other MANAGEMENT_KEY", async () => {
|
||||||
try {
|
await identity.execute(
|
||||||
await identity.removeKey(accounts[0], {from: accounts[0]})
|
identity.address,
|
||||||
} catch(e) {
|
0,
|
||||||
//nothing
|
idUtils.encode.removeKey(accounts[0], idUtils.purposes.MANAGEMENT),
|
||||||
}
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[0]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])),
|
||||||
1,
|
idUtils.purposes.MANAGEMENT,
|
||||||
identity.address+".getKeyType("+accounts[0]+") is not MANAGEMENT_KEY")
|
identity.address+".getKeyType("+accounts[0]+") is not 1")
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fire KeyRemoved(address indexed key, uint256 indexed type)", async () => {
|
it("fire KeyRemoved(address indexed key, uint256 indexed type)", async () => {
|
||||||
await identity.addKey(accounts[1], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
identity.removeKey(accounts[1], {from: accounts[0]})
|
identity.address,
|
||||||
const keyRemoved = await TestUtils.listenForEvent(identity.KeyRemoved())
|
0,
|
||||||
assert(keyRemoved.key, accounts[1], "Key is not correct")
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
assert(keyRemoved.keyType, 2, "Type is not correct")
|
{from: accounts[0]}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.ACTION),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
const keyRemoved = await TestUtils.listenForEvent(identity.KeyRemoved());
|
||||||
|
assert(keyRemoved.key, TestUtils.addressToBytes32(accounts[1]), "Key is not correct");
|
||||||
|
assert(keyRemoved.keyType, idUtils.types.ADDRESS, "Type is not correct");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("getKeyType(address _key)", () => {
|
describe("getKeyPurpose(address _key)", () => {
|
||||||
|
|
||||||
it("should start only with initializer as only key", async () => {
|
it("should start only with initializer as only key", async () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[0]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])),
|
||||||
1,
|
idUtils.purposes.MANAGEMENT,
|
||||||
identity.address+".getKeyType("+accounts[0]+") is not correct")
|
identity.address+".getKeyPurpose("+accounts[0]+") is not correct")
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
0,
|
idUtils.purposes.NONE,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get type 2 after addKey type 2", async () => {
|
it("should get type 2 after addKey type 2", async () => {
|
||||||
await identity.addKey(accounts[1], 2, {from: accounts[0]})
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
2,
|
idUtils.purposes.ACTION,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get type 999 after addKey type 999", async () => {
|
it("should get type 3 after addKey type 3", async () => {
|
||||||
await identity.addKey(accounts[1], 999, {from: accounts[0]})
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.CLAIM_SIGNER, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await identity.getKeyType(accounts[1]),
|
await identity.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])),
|
||||||
999,
|
idUtils.purposes.CLAIM_SIGNER,
|
||||||
identity.address+".getKeyType("+accounts[1]+") is not correct")
|
identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
describe("getKeysByType(uint256 _type)", () => {
|
describe("getKeysByType(uint256 _type)", () => {
|
||||||
|
|
||||||
it("at initialization", async () => {
|
it("at initialization", async () => {
|
||||||
|
@ -177,60 +273,179 @@ contract('Identity', function(accounts) {
|
||||||
it("after removeKey", async () => {
|
it("after removeKey", async () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("after replaceKey", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("replaceKey(address _oldKey, address _newKey)", () => {
|
|
||||||
|
|
||||||
it("MANAGEMENT_KEY replace itself (alone)", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("MANAGEMENT_KEY replace a key between others", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("MANAGEMENT_KEY replace the first key", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("MANAGEMENT_KEY replace the last key", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fire KeyReplaced(address indexed oldKey, address indexed newKey, uint256 indexed type)", async () => {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
describe("execute(address _to, uint256 _value, bytes _data)", () => {
|
describe("execute(address _to, uint256 _value, bytes _data)", () => {
|
||||||
|
let testContractInstance;
|
||||||
|
let functionPayload;
|
||||||
|
|
||||||
|
it("Identity should receive ether", async() => {
|
||||||
|
|
||||||
|
const amountToSend = web3.toWei(0.05, "ether");
|
||||||
|
|
||||||
|
let idBalance0 = web3.eth.getBalance(identity.address);
|
||||||
|
|
||||||
|
await web3.eth.sendTransaction({from:accounts[0], to:identity.address, value: amountToSend})
|
||||||
|
|
||||||
|
let idBalance1 = web3.eth.getBalance(identity.address);
|
||||||
|
|
||||||
|
assert.equal(idBalance0.toNumber() + amountToSend, idBalance1.toNumber(), identity.address + " did not receive ether");
|
||||||
|
});
|
||||||
|
|
||||||
it("ACTOR_KEY execute arbitrary transaction", async () => {
|
it("ACTOR_KEY execute arbitrary transaction", async () => {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
testContractInstance = await TestContract.new({from: accounts[0]});
|
||||||
|
|
||||||
|
functionPayload = web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'test',
|
||||||
|
type: 'function',
|
||||||
|
inputs: []
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
testContractInstance.address,
|
||||||
|
0,
|
||||||
|
functionPayload,
|
||||||
|
{from: accounts[1]}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.notEqual(
|
||||||
|
await TestUtils.listenForEvent(testContractInstance.TestFunctionExecuted()),
|
||||||
|
undefined,
|
||||||
|
"Test function was not executed");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("MANAGEMENT_KEY execute arbitrary transaction", async () => {
|
it("MANAGEMENT_KEY cannot execute arbitrary transaction", async () => {
|
||||||
|
try {
|
||||||
|
await identity.execute(
|
||||||
|
testContractInstance.address,
|
||||||
|
0,
|
||||||
|
functionPayload,
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Other keys NOT execute arbitrary transaction", async () => {
|
it("Other keys NOT execute arbitrary transaction", async () => {
|
||||||
|
try {
|
||||||
|
await identity.execute(
|
||||||
|
testContractInstance.address,
|
||||||
|
0,
|
||||||
|
functionPayload,
|
||||||
|
{from: accounts[3]}
|
||||||
|
);
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("ACTION_KEY should send ether from contract", async () => {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Adding funds to contract
|
||||||
|
await web3.eth.sendTransaction({from:accounts[0], to:identity.address, value: web3.toWei(0.05, "ether")})
|
||||||
|
|
||||||
|
const amountToSend = web3.toWei(0.01, "ether");
|
||||||
|
|
||||||
|
let idBalance0 = web3.eth.getBalance(identity.address);
|
||||||
|
let a2Balance0 = web3.eth.getBalance(accounts[2]);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
accounts[2],
|
||||||
|
amountToSend,
|
||||||
|
'',
|
||||||
|
{from: accounts[1]}
|
||||||
|
);
|
||||||
|
|
||||||
|
let idBalance1 = web3.eth.getBalance(identity.address);
|
||||||
|
let a2Balance1 = web3.eth.getBalance(accounts[2]);
|
||||||
|
|
||||||
|
assert(idBalance1.toNumber, idBalance0.toNumber - amountToSend, "Contract did not send ether");
|
||||||
|
assert(a2Balance1.toNumber, a2Balance0.toNumber + amountToSend, accounts[2] + " did not receive ether");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fire ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data)", async () => {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
testContractInstance.address,
|
||||||
|
0,
|
||||||
|
functionPayload,
|
||||||
|
{from: accounts[1]}
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionRequested = await TestUtils.listenForEvent(identity.ExecutionRequested());
|
||||||
|
assert(executionRequested.to, testContractInstance.address, "To is not correct");
|
||||||
|
assert(executionRequested.value, 0, "Value is not correct");
|
||||||
|
assert(executionRequested.data, functionPayload, "Data is not correct");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fire Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data)", async () => {
|
it("fire Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data)", async () => {
|
||||||
|
await identity.execute(
|
||||||
|
identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS),
|
||||||
|
{from: accounts[0]}
|
||||||
|
);
|
||||||
|
|
||||||
|
await identity.execute(
|
||||||
|
testContractInstance.address,
|
||||||
|
0,
|
||||||
|
functionPayload,
|
||||||
|
{from: accounts[1]}
|
||||||
|
);
|
||||||
|
|
||||||
|
const executed = await TestUtils.listenForEvent(identity.Executed());
|
||||||
|
assert(executed.to, testContractInstance.address, "To is not correct");
|
||||||
|
assert(executed.value, 0, "Value is not correct");
|
||||||
|
assert(executed.data, functionPayload, "Data is not correct");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
describe("setMinimumApprovalsByKeyPurpose(uint256 _type, uint8 _minimumApprovals)", () => {
|
||||||
|
it("MANAGEMENT_KEY should set minimum approvals for MANAGEMENT_KEYs", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("MANAGEMENT_KEY should set minimum approvals for ACTION_KEYs", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ACTION_KEY should not be able to set minimum approvals", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Other keys should not be able to set minimum approvals", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("approve(bytes32 _id, bool _approve)", () => {
|
describe("approve(bytes32 _id, bool _approve)", () => {
|
||||||
|
|
||||||
it("MANAGEMENT_KEY should approve a claim", async () => {
|
it("MANAGEMENT_KEY should approve a claim", async () => {
|
||||||
|
@ -241,6 +456,10 @@ contract('Identity', function(accounts) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("2 out of 3 MANAGEMENT_KEY should approve a transaction and execute it", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it("fire Approved(uint256 indexed executionId, bool approved)", async () => {
|
it("fire Approved(uint256 indexed executionId, bool approved)", async () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -282,5 +501,5 @@ contract('Identity', function(accounts) {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
const TestUtils = require("../utils/testUtils.js")
|
||||||
|
var ethUtils = require('ethereumjs-util')
|
||||||
|
|
||||||
|
const Identity = artifacts.require("./identity/Identity.sol");
|
||||||
|
|
||||||
|
contract('Identity - Extended Functionality', function(accounts) {
|
||||||
|
|
||||||
|
let identity;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
identity = await Identity.new({from: accounts[0]});
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe("Identity()", () => {
|
||||||
|
|
||||||
|
let privateKey = new Buffer('61bffea9215f65164ad18b45aff1436c0c165d0d5dd2087ef61b4232ba6d2c1a', 'hex')
|
||||||
|
let publicKey = ethUtils.privateToPublic(privateKey);
|
||||||
|
let pkSha = web3.sha3(publicKey.toString('hex'), {encoding: 'hex'});
|
||||||
|
|
||||||
|
it("Add ECDSA Management Key", async () => {
|
||||||
|
|
||||||
|
await identity.addKey(pkSha, 2, 1, {from: accounts[0]})
|
||||||
|
|
||||||
|
await identity.addPublicKey(pkSha, '0x' + publicKey.toString('hex'), {from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await identity.getPublicKey(pkSha, {from: accounts[0]}),
|
||||||
|
'0x' + publicKey.toString('hex'),
|
||||||
|
identity.address+".getPublicKey("+pkSha+") is not correct");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Test Execution", async () => {
|
||||||
|
|
||||||
|
let to = accounts[1];
|
||||||
|
let value = 100;
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
let message = ethUtils.toBuffer("SignedMessage");
|
||||||
|
let msgHash = ethUtils.hashPersonalMessage(message);
|
||||||
|
let sig = ethUtils.ecsign(msgHash, privateKey);
|
||||||
|
|
||||||
|
let r = '0x' + sig.r.toString('hex');
|
||||||
|
let s = '0x' + sig.s.toString('hex');
|
||||||
|
let v = sig.v;
|
||||||
|
|
||||||
|
|
||||||
|
await identity.addKey(pkSha, 2, 1, {from: accounts[0]})
|
||||||
|
|
||||||
|
await identity.addPublicKey(pkSha, '0x' + publicKey.toString('hex'), {from: accounts[0]});
|
||||||
|
|
||||||
|
let tx = await identity.executeECDSA(to, value, data, pkSha, '0x' + msgHash.toString('hex'), v, r, s, {from: accounts[0]});
|
||||||
|
|
||||||
|
// TODO Assert ExecutionRequested Event
|
||||||
|
console.log(tx)
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,191 @@
|
||||||
|
const web3EthAbi = require("web3-eth-abi");
|
||||||
|
|
||||||
|
const _types = {
|
||||||
|
ADDRESS: 0,
|
||||||
|
ECDSA: 1,
|
||||||
|
RSA: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const _purposes = {
|
||||||
|
MANAGEMENT: 1,
|
||||||
|
ACTION: 2,
|
||||||
|
CLAIM_SIGNER: 3,
|
||||||
|
ENCRYPTION: 4,
|
||||||
|
|
||||||
|
NONE: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexToBytes32 = (input) => {
|
||||||
|
input = input.replace(/^0x/i,'');
|
||||||
|
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + input;
|
||||||
|
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _addKey = function(key, purpose, type){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,64}$/i.test(key))
|
||||||
|
throw new Error('Key "'+ key +'" is not a valid hex string');
|
||||||
|
|
||||||
|
if (Object.values(_purposes).indexOf(purpose) == -1)
|
||||||
|
throw new Error('Purpose "'+ purpose +'" is not a valid purpose');
|
||||||
|
|
||||||
|
if (Object.values(_types).indexOf(type) == -1)
|
||||||
|
throw new Error('Type "'+ type +'" is not a valid type');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'addKey',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'bytes32',
|
||||||
|
name: '_key'
|
||||||
|
},{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_purpose'
|
||||||
|
},{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_type'
|
||||||
|
}]
|
||||||
|
}, [hexToBytes32(key), purpose, type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _removeKey = function(key, purpose){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,64}$/i.test(key))
|
||||||
|
throw new Error('Key "'+ key +'" is not a valid hex string');
|
||||||
|
|
||||||
|
if (Object.values(_purposes).indexOf(purpose) == -1)
|
||||||
|
throw new Error('Purpose "'+ purpose +'" is not a valid purpose');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'removeKey',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'bytes32',
|
||||||
|
name: '_key'
|
||||||
|
},{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_purpose'
|
||||||
|
}]
|
||||||
|
}, [hexToBytes32(key), purpose]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const _setMinimumApprovalsByKeyType = function(type, minimumApprovals) {
|
||||||
|
|
||||||
|
if (Object.values(_types).indexOf(type) == -1)
|
||||||
|
throw new Error('Type "'+ type +'" is not a valid type');
|
||||||
|
|
||||||
|
// TODO valdate minimumApprovals
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'setMinimumApprovalsByKeyType',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_type'
|
||||||
|
},{
|
||||||
|
type: 'uint8',
|
||||||
|
name: '_minimumApprovals'
|
||||||
|
}]
|
||||||
|
}, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const _setupRecovery = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'setupRecovery',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'address',
|
||||||
|
name: '_recoveryContract'
|
||||||
|
}]
|
||||||
|
}, [address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _managerReset = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'managerReset',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'address',
|
||||||
|
name: '_newKey'
|
||||||
|
}]
|
||||||
|
}, [address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const _updateUpdatableInstance = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'updateUpdatableInstance',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'address',
|
||||||
|
name: '_kernel'
|
||||||
|
}]
|
||||||
|
}, [address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _updateRequestUpdatableInstance = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'updateRequestUpdatableInstance',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'address',
|
||||||
|
name: '_kernel'
|
||||||
|
}]
|
||||||
|
}, [address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _updateConfirmUpdatableInstance = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'updateConfirmUpdatableInstance',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'address',
|
||||||
|
name: '_kernel'
|
||||||
|
}]
|
||||||
|
}, [address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _updateCancelUpdatableInstance = function(address){
|
||||||
|
if(!/^(0x)?[0-9a-f]{0,40}$/i.test(address))
|
||||||
|
throw new Error('Address "'+ address +'" is not a valid Ethereum address.');
|
||||||
|
|
||||||
|
return web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'updateCancelUpdatableInstance',
|
||||||
|
type: 'function',
|
||||||
|
inputs: []
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
types: _types,
|
||||||
|
purposes: _purposes,
|
||||||
|
encode: {
|
||||||
|
addKey: _addKey,
|
||||||
|
removeKey: _removeKey,
|
||||||
|
setMinimumApprovalsByKeyType: _setMinimumApprovalsByKeyType,
|
||||||
|
setupRecovery: _setupRecovery,
|
||||||
|
managerReset: _managerReset,
|
||||||
|
updateUpdatableInstance: _updateUpdatableInstance,
|
||||||
|
updateRequestUpdatableInstance: _updateRequestUpdatableInstance,
|
||||||
|
updateConfirmUpdatableInstance: _updateConfirmUpdatableInstance,
|
||||||
|
updateCancelUpdatableInstance: _updateCancelUpdatableInstance
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue