mirror of
https://github.com/status-im/snt-gas-relay.git
synced 2025-02-28 13:40:29 +00:00
Merge remote-tracking branch 'origin/150-gas-abstraction' into 000-gas-relayer-node
This commit is contained in:
commit
6c90a57cad
77
contracts/deploy/DelayedUpdatableInstance.sol
Normal file
77
contracts/deploy/DelayedUpdatableInstance.sol
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestUpdatableInstance(
|
||||||
|
address _kernel
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
uint activation = block.timestamp + 30 days;
|
||||||
|
update = Update(_kernel, activation);
|
||||||
|
emit 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;
|
||||||
|
emit UpdateConfirmed(kernel, pending.kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCancelUpdatableInstance()
|
||||||
|
external
|
||||||
|
{
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
delete update;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev returns configured kernel
|
||||||
|
* @return kernel address
|
||||||
|
*/
|
||||||
|
function targetDelegatedCall()
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns(address)
|
||||||
|
{
|
||||||
|
return kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
20
contracts/deploy/DelayedUpdatableInstanceStorage.sol
Normal file
20
contracts/deploy/DelayedUpdatableInstanceStorage.sol
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title DelayedUpdatableInstanceStorage
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Defines kernel vars that Kernel contract share with DelayedUpdatableInstance.
|
||||||
|
* Important to avoid overwriting wrong storage pointers is that
|
||||||
|
* InstanceStorage should be always the first contract at heritance.
|
||||||
|
*/
|
||||||
|
contract DelayedUpdatableInstanceStorage {
|
||||||
|
// protected zone start (InstanceStorage vars)
|
||||||
|
address public kernel;
|
||||||
|
Update public update;
|
||||||
|
|
||||||
|
struct Update {
|
||||||
|
address kernel;
|
||||||
|
uint256 activation;
|
||||||
|
}
|
||||||
|
// protected zone end
|
||||||
|
}
|
35
contracts/deploy/DelegatedCall.sol
Normal file
35
contracts/deploy/DelegatedCall.sol
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title DelegatedCall
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Abstract contract that delegates calls by `delegated` modifier to result of `targetDelegatedCall()`
|
||||||
|
* Important to avoid overwriting wrong storage pointers is that never define storage to this contract
|
||||||
|
*/
|
||||||
|
contract DelegatedCall {
|
||||||
|
/**
|
||||||
|
* @dev delegates the call of this function
|
||||||
|
*/
|
||||||
|
modifier delegated {
|
||||||
|
//require successfull delegate call to remote `_target()`
|
||||||
|
require(targetDelegatedCall().delegatecall(msg.data));
|
||||||
|
assembly {
|
||||||
|
let outSize := returndatasize
|
||||||
|
let outDataPtr := mload(0x40) //load memory
|
||||||
|
returndatacopy(outDataPtr, 0, outSize) //copy last return into pointer
|
||||||
|
return(outDataPtr, outSize)
|
||||||
|
}
|
||||||
|
assert(false); //should never reach here
|
||||||
|
_; //never will execute local logic
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev defines the address for delegation of calls
|
||||||
|
*/
|
||||||
|
function targetDelegatedCall()
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns(address);
|
||||||
|
|
||||||
|
}
|
90
contracts/deploy/Factory.sol
Normal file
90
contracts/deploy/Factory.sol
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "../common/Controlled.sol";
|
||||||
|
|
||||||
|
contract Factory is Controlled {
|
||||||
|
|
||||||
|
event NewKernel(address newKernel, bytes32 codeHash);
|
||||||
|
|
||||||
|
struct Version {
|
||||||
|
uint256 blockNumber;
|
||||||
|
uint256 timestamp;
|
||||||
|
address kernel;
|
||||||
|
bytes32 codeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping (address => uint256) versionMap;
|
||||||
|
|
||||||
|
Version[] versionLog;
|
||||||
|
uint256 latestUpdate;
|
||||||
|
address latestKernel;
|
||||||
|
|
||||||
|
function Factory(address _kernel)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
_setKernel(_kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKernel(address _kernel)
|
||||||
|
external
|
||||||
|
onlyController
|
||||||
|
{
|
||||||
|
_setKernel(_kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersion(uint256 index)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns(
|
||||||
|
uint256 blockNumber,
|
||||||
|
uint256 timestamp,
|
||||||
|
address kernel,
|
||||||
|
bytes32 codeHash
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
versionLog[index].blockNumber,
|
||||||
|
versionLog[index].timestamp,
|
||||||
|
versionLog[index].kernel,
|
||||||
|
versionLog[index].codeHash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCodeHash(address _addr)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 codeHash)
|
||||||
|
{
|
||||||
|
bytes memory o_code;
|
||||||
|
uint size;
|
||||||
|
assembly {
|
||||||
|
// retrieve the size of the code, this needs assembly
|
||||||
|
size := extcodesize(_addr)
|
||||||
|
}
|
||||||
|
require (size > 0);
|
||||||
|
assembly {
|
||||||
|
// allocate output byte array - this could also be done without assembly
|
||||||
|
// by using o_code = new bytes(size)
|
||||||
|
o_code := mload(0x40)
|
||||||
|
// new "memory end" including padding
|
||||||
|
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
|
||||||
|
// store length in memory
|
||||||
|
mstore(o_code, size)
|
||||||
|
// actually retrieve the code, this needs assembly
|
||||||
|
extcodecopy(_addr, add(o_code, 0x20), 0, size)
|
||||||
|
}
|
||||||
|
codeHash = keccak256(o_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setKernel(address _kernel)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
require(_kernel != latestKernel);
|
||||||
|
bytes32 _codeHash = getCodeHash(_kernel);
|
||||||
|
versionMap[_kernel] = versionLog.length;
|
||||||
|
versionLog.push(Version({blockNumber: block.number, timestamp: block.timestamp, kernel: _kernel, codeHash: _codeHash}));
|
||||||
|
latestUpdate = block.timestamp;
|
||||||
|
latestKernel = _kernel;
|
||||||
|
emit NewKernel(_kernel, _codeHash);
|
||||||
|
}
|
||||||
|
}
|
38
contracts/deploy/Instance.sol
Normal file
38
contracts/deploy/Instance.sol
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "./InstanceStorage.sol";
|
||||||
|
import "./DelegatedCall.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Instance
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Contract that forward everything through delegatecall to defined kernel
|
||||||
|
*/
|
||||||
|
contract Instance is InstanceStorage, DelegatedCall {
|
||||||
|
|
||||||
|
function Instance(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
|
||||||
|
view
|
||||||
|
returns(address)
|
||||||
|
{
|
||||||
|
return kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
15
contracts/deploy/InstanceStorage.sol
Normal file
15
contracts/deploy/InstanceStorage.sol
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 InstanceStorage {
|
||||||
|
// protected zone start (InstanceStorage vars)
|
||||||
|
address public kernel;
|
||||||
|
// protected zone end
|
||||||
|
}
|
28
contracts/deploy/UpdatableInstance.sol
Normal file
28
contracts/deploy/UpdatableInstance.sol
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "./Instance.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title UpdatableInstance
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @dev Contract that can be updated by a call from itself.
|
||||||
|
*/
|
||||||
|
contract UpdatableInstance is Instance {
|
||||||
|
|
||||||
|
event InstanceUpdated(address oldKernel, address newKernel);
|
||||||
|
|
||||||
|
function UpdatableInstance(address _kernel)
|
||||||
|
Instance(_kernel)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUpdatableInstance(address _kernel) external {
|
||||||
|
require(msg.sender == address(this));
|
||||||
|
InstanceUpdated(kernel, _kernel);
|
||||||
|
kernel = _kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
contracts/identity/ERC725.sol
Normal file
29
contracts/identity/ERC725.sol
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
contract ERC725 {
|
||||||
|
|
||||||
|
uint256 constant MANAGEMENT_KEY = 1;
|
||||||
|
uint256 constant ACTION_KEY = 2;
|
||||||
|
uint256 constant CLAIM_SIGNER_KEY = 3;
|
||||||
|
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);
|
||||||
|
|
||||||
|
struct Key {
|
||||||
|
uint256 purpose; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc.
|
||||||
|
uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc.
|
||||||
|
bytes32 key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKey(bytes32 _key, uint256 _purpose) public view returns(uint256 purpose, uint256 keyType, bytes32 key);
|
||||||
|
function getKeyPurpose(bytes32 _key) public view returns(uint256[] purpose);
|
||||||
|
function getKeysByPurpose(uint256 _purpose) public view returns(bytes32[] keys);
|
||||||
|
function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success);
|
||||||
|
function removeKey(bytes32 _key, uint256 _purpose) public returns (bool success);
|
||||||
|
function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId);
|
||||||
|
function approve(uint256 _id, bool _approve) public returns (bool success);
|
||||||
|
}
|
23
contracts/identity/ERC735.sol
Normal file
23
contracts/identity/ERC735.sol
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
contract ERC735 {
|
||||||
|
|
||||||
|
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, uint256 scheme, address indexed issuer, bytes signature, bytes data, 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, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
|
||||||
|
|
||||||
|
struct Claim {
|
||||||
|
uint256 claimType;
|
||||||
|
uint256 scheme;
|
||||||
|
address issuer; // msg.sender
|
||||||
|
bytes signature; // this.address + claimType + data
|
||||||
|
bytes data;
|
||||||
|
string uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClaim(bytes32 _claimId) public view returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
|
||||||
|
function getClaimIdsByType(uint256 _claimType) public view returns(bytes32[] claimIds);
|
||||||
|
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);
|
||||||
|
}
|
620
contracts/identity/Identity.sol
Normal file
620
contracts/identity/Identity.sol
Normal file
@ -0,0 +1,620 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "./ERC725.sol";
|
||||||
|
import "./ERC735.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract Identity is ERC725, ERC735 {
|
||||||
|
|
||||||
|
mapping (bytes32 => Key) keys;
|
||||||
|
mapping (uint256 => bytes32[]) keysByPurpose;
|
||||||
|
mapping (bytes32 => Claim) claims;
|
||||||
|
mapping (uint256 => bytes32[]) claimsByType;
|
||||||
|
|
||||||
|
mapping (bytes32 => uint256) indexes;
|
||||||
|
mapping (uint256 => Transaction) txx;
|
||||||
|
mapping (uint256 => uint256) purposeThreshold;
|
||||||
|
|
||||||
|
uint256 nonce;
|
||||||
|
address recoveryContract;
|
||||||
|
bytes32 recoveryManager;
|
||||||
|
|
||||||
|
struct Transaction {
|
||||||
|
bool valid;
|
||||||
|
address to;
|
||||||
|
uint256 value;
|
||||||
|
bytes data;
|
||||||
|
uint256 nonce;
|
||||||
|
uint256 approverCount;
|
||||||
|
mapping(bytes32 => bool) approvals;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier managerOnly {
|
||||||
|
require(
|
||||||
|
isKeyPurpose(keccak256(msg.sender), MANAGEMENT_KEY)
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier managementOnly {
|
||||||
|
if(msg.sender == address(this)) {
|
||||||
|
_;
|
||||||
|
} else {
|
||||||
|
require(isKeyPurpose(keccak256(msg.sender), MANAGEMENT_KEY));
|
||||||
|
if (purposeThreshold[MANAGEMENT_KEY] == 1) {
|
||||||
|
_;
|
||||||
|
} else {
|
||||||
|
execute(address(this), 0, msg.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modifier recoveryOnly {
|
||||||
|
require(
|
||||||
|
recoveryContract != address(0) &&
|
||||||
|
msg.sender == recoveryContract
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier keyPurposeOnly(bytes32 _key, uint256 _purpose) {
|
||||||
|
require(isKeyPurpose(_key, _purpose));
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier managerOrActor(bytes32 _key) {
|
||||||
|
require(
|
||||||
|
isKeyPurpose(_key, MANAGEMENT_KEY) ||
|
||||||
|
isKeyPurpose(_key, ACTION_KEY)
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier validECDSAKey (
|
||||||
|
bytes32 _key,
|
||||||
|
bytes32 _signHash,
|
||||||
|
uint8 _v,
|
||||||
|
bytes32 _r,
|
||||||
|
bytes32 _s
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
_key == keccak256(
|
||||||
|
ecrecover(
|
||||||
|
keccak256("\x19Ethereum Signed Message:\n32", _signHash),
|
||||||
|
_v,
|
||||||
|
_r,
|
||||||
|
_s
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
require(keys[_key].purpose != 0);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
_constructIdentity(keccak256(msg.sender));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ()
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function managerReset(bytes32 _newKey)
|
||||||
|
public
|
||||||
|
recoveryOnly
|
||||||
|
{
|
||||||
|
recoveryManager = _newKey;
|
||||||
|
_addKey(keccak256(recoveryManager), MANAGEMENT_KEY, 0);
|
||||||
|
purposeThreshold[MANAGEMENT_KEY] = keysByPurpose[MANAGEMENT_KEY].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processManagerReset(uint256 _limit)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
require(recoveryManager != 0);
|
||||||
|
uint256 limit = _limit;
|
||||||
|
bytes32 newKey = recoveryManager;
|
||||||
|
bytes32[] memory managers = keysByPurpose[MANAGEMENT_KEY];
|
||||||
|
uint256 totalManagers = managers.length;
|
||||||
|
|
||||||
|
if (limit == 0) {
|
||||||
|
limit = totalManagers;
|
||||||
|
}
|
||||||
|
|
||||||
|
purposeThreshold[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) {
|
||||||
|
delete recoveryManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose,
|
||||||
|
uint256 _type
|
||||||
|
)
|
||||||
|
public
|
||||||
|
managementOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
_addKey(_key, _purpose, _type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceKey(
|
||||||
|
bytes32 _oldKey,
|
||||||
|
bytes32 _newKey,
|
||||||
|
uint256 _newType
|
||||||
|
)
|
||||||
|
public
|
||||||
|
managementOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
uint256 purpose = keys[_oldKey].purpose;
|
||||||
|
_addKey(_newKey, purpose, _newType);
|
||||||
|
_removeKey(_oldKey, purpose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose
|
||||||
|
)
|
||||||
|
public
|
||||||
|
managementOnly
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
_removeKey(_key, _purpose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (uint256 executionId)
|
||||||
|
{
|
||||||
|
uint256 requiredKey = _to == address(this) ? MANAGEMENT_KEY : ACTION_KEY;
|
||||||
|
if (purposeThreshold[requiredKey] == 1) {
|
||||||
|
executionId = nonce; //(?) useless in this case
|
||||||
|
nonce++; //(?) should increment
|
||||||
|
require(isKeyPurpose(keccak256(msg.sender), requiredKey));
|
||||||
|
_to.call.value(_value)(_data); //(?) success not used
|
||||||
|
emit Executed(executionId, _to, _value, _data); //no information on success
|
||||||
|
} else {
|
||||||
|
executionId = _execute(_to, _value, _data);
|
||||||
|
approve(executionId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function approve(uint256 _id, bool _approval)
|
||||||
|
public
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
return _approve(keccak256(msg.sender), _id, _approval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMinimumApprovalsByKeyType(
|
||||||
|
uint256 _purpose,
|
||||||
|
uint256 _minimumApprovals
|
||||||
|
)
|
||||||
|
public
|
||||||
|
managementOnly
|
||||||
|
{
|
||||||
|
require(_minimumApprovals > 0);
|
||||||
|
require(_minimumApprovals <= keysByPurpose[_purpose].length);
|
||||||
|
purposeThreshold[_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(isKeyPurpose(keccak256(msg.sender), CLAIM_SIGNER_KEY));
|
||||||
|
_execute(address(this), 0, msg.data);
|
||||||
|
emit 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.
|
||||||
|
|
||||||
|
uint256 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
|
||||||
|
view
|
||||||
|
returns(uint256 purpose, uint256 keyType, bytes32 key)
|
||||||
|
{
|
||||||
|
Key storage myKey = keys[keccak256(_key, _purpose)];
|
||||||
|
return (myKey.purpose, myKey.keyType, myKey.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyPurpose(bytes32 _key, uint256 _purpose)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return keys[keccak256(_key, _purpose)].purpose == _purpose;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyPurpose(bytes32 _key)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns(uint256[] purpose)
|
||||||
|
{
|
||||||
|
|
||||||
|
uint256[] memory purposeHolder = new uint256[](4);
|
||||||
|
uint8 counter = 0;
|
||||||
|
|
||||||
|
if (isKeyPurpose(_key, MANAGEMENT_KEY)) {
|
||||||
|
purposeHolder[counter] = MANAGEMENT_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyPurpose(_key, ACTION_KEY)) {
|
||||||
|
purposeHolder[counter] = ACTION_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyPurpose(_key, CLAIM_SIGNER_KEY)) {
|
||||||
|
purposeHolder[counter] = CLAIM_SIGNER_KEY;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyPurpose(_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
|
||||||
|
view
|
||||||
|
returns(bytes32[])
|
||||||
|
{
|
||||||
|
return keysByPurpose[_purpose];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClaim(bytes32 _claimId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
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
|
||||||
|
view
|
||||||
|
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,
|
||||||
|
uint256 _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
|
||||||
|
managementOnly
|
||||||
|
{
|
||||||
|
require(recoveryContract == address(0));
|
||||||
|
recoveryContract = _recoveryContract;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _constructIdentity(bytes32 _managerKey)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
require(keysByPurpose[MANAGEMENT_KEY].length == 0);
|
||||||
|
require(purposeThreshold[MANAGEMENT_KEY] == 0);
|
||||||
|
_addKey(_managerKey, MANAGEMENT_KEY, 0);
|
||||||
|
_addKey(_managerKey, ACTION_KEY, 0);
|
||||||
|
|
||||||
|
purposeThreshold[MANAGEMENT_KEY] = 1;
|
||||||
|
purposeThreshold[ACTION_KEY] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 executionId)
|
||||||
|
{
|
||||||
|
executionId = nonce;
|
||||||
|
nonce++;
|
||||||
|
txx[executionId] = Transaction({
|
||||||
|
valid: true,
|
||||||
|
to: _to,
|
||||||
|
value: _value,
|
||||||
|
data: _data,
|
||||||
|
nonce: nonce,
|
||||||
|
approverCount: 0
|
||||||
|
});
|
||||||
|
emit ExecutionRequested(executionId, _to, _value, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _approve(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _id,
|
||||||
|
bool _approval
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns(bool success) //(?) should return approved instead of success?
|
||||||
|
{
|
||||||
|
|
||||||
|
Transaction memory trx = txx[_id];
|
||||||
|
require(trx.valid);
|
||||||
|
uint256 requiredKeyPurpose = trx.to == address(this) ? MANAGEMENT_KEY : ACTION_KEY;
|
||||||
|
require(isKeyPurpose(_key, requiredKeyPurpose));
|
||||||
|
bytes32 keyHash = keccak256(_key, requiredKeyPurpose);
|
||||||
|
require(txx[_id].approvals[keyHash] != _approval);
|
||||||
|
|
||||||
|
if (_approval) {
|
||||||
|
trx.approverCount++;
|
||||||
|
} else {
|
||||||
|
trx.approverCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit Approved(_id, _approval);
|
||||||
|
|
||||||
|
if (trx.approverCount < purposeThreshold[requiredKeyPurpose]) {
|
||||||
|
txx[_id].approvals[keyHash] = _approval;
|
||||||
|
txx[_id] = trx;
|
||||||
|
} else {
|
||||||
|
delete txx[_id];
|
||||||
|
//(?) success should be included in event?
|
||||||
|
success = address(trx.to).call.value(trx.value)(trx.data);
|
||||||
|
emit Executed(_id, trx.to, 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
|
||||||
|
);
|
||||||
|
keys[keyHash] = Key(_purpose, _type, _key);
|
||||||
|
indexes[keyHash] = keysByPurpose[_purpose].push(_key) - 1;
|
||||||
|
emit KeyAdded(_key, _purpose, _type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeKey(
|
||||||
|
bytes32 _key,
|
||||||
|
uint256 _purpose
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
if (_purpose == MANAGEMENT_KEY) {
|
||||||
|
require(keysByPurpose[MANAGEMENT_KEY].length > purposeThreshold[MANAGEMENT_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes32 keyHash = keccak256(_key, _purpose);
|
||||||
|
Key memory myKey = keys[keyHash];
|
||||||
|
uint256 index = indexes[keyHash];
|
||||||
|
bytes32 indexReplacer = keysByPurpose[_purpose][keysByPurpose[_purpose].length - 1];
|
||||||
|
|
||||||
|
keysByPurpose[_purpose][index] = indexReplacer;
|
||||||
|
indexes[keccak256(indexReplacer, _purpose)] = index;
|
||||||
|
keysByPurpose[_purpose].length--;
|
||||||
|
|
||||||
|
delete indexes[keyHash];
|
||||||
|
delete keys[keyHash];
|
||||||
|
|
||||||
|
emit KeyRemoved(myKey.key, myKey.purpose, myKey.keyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
emit 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);
|
||||||
|
claims[_claimHash] = Claim({
|
||||||
|
claimType: _claimType,
|
||||||
|
scheme: _scheme,
|
||||||
|
issuer: _issuer,
|
||||||
|
signature: _signature,
|
||||||
|
data: _data,
|
||||||
|
uri: _uri
|
||||||
|
});
|
||||||
|
emit ClaimChanged(
|
||||||
|
_claimHash,
|
||||||
|
_claimType,
|
||||||
|
_scheme,
|
||||||
|
_issuer,
|
||||||
|
_signature,
|
||||||
|
_data,
|
||||||
|
_uri
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
35
contracts/identity/IdentityFactory.sol
Normal file
35
contracts/identity/IdentityFactory.sol
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIdentity()
|
||||||
|
external
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
return createIdentity(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIdentity(address _idOwner)
|
||||||
|
public
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
IdentityKernel instance = IdentityKernel(new DelayedUpdatableInstance(address(latestKernel)));
|
||||||
|
instance.initIdentity(_idOwner);
|
||||||
|
emit IdentityCreated(address(instance));
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
364
contracts/identity/IdentityGasRelay.sol
Normal file
364
contracts/identity/IdentityGasRelay.sol
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
pragma solidity ^0.4.21;
|
||||||
|
|
||||||
|
import "./Identity.sol";
|
||||||
|
import "../token/ERC20Token.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title IdentityGasRelay
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @notice enables economic abstraction for Identity
|
||||||
|
*/
|
||||||
|
contract IdentityGasRelay is Identity {
|
||||||
|
|
||||||
|
bytes4 public constant CALL_PREFIX = bytes4(keccak256("callGasRelay(address,uint256,bytes32,uint256,uint256,address)"));
|
||||||
|
bytes4 public constant APPROVEANDCALL_PREFIX = bytes4(keccak256("approveAndCallGasRelay(address,address,uint256,bytes32,uint256,uint256)"));
|
||||||
|
|
||||||
|
event ExecutedGasRelayed(bytes32 signHash, bool success);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice include ethereum signed callHash in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken`
|
||||||
|
* allows identity of being controlled without requiring ether in key balace
|
||||||
|
* @param _to destination of call
|
||||||
|
* @param _value call value (ether)
|
||||||
|
* @param _data call data
|
||||||
|
* @param _nonce current identity nonce
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasLimit minimal gasLimit required to execute this call
|
||||||
|
* @param _gasToken token being used for paying `msg.sender`
|
||||||
|
* @param _messageSignatures rsv concatenated ethereum signed message signatures required
|
||||||
|
*/
|
||||||
|
function callGasRelayed(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data,
|
||||||
|
uint _nonce,
|
||||||
|
uint _gasPrice,
|
||||||
|
uint _gasLimit,
|
||||||
|
address _gasToken,
|
||||||
|
bytes _messageSignatures
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
uint startGas = gasleft();
|
||||||
|
//verify transaction parameters
|
||||||
|
require(startGas >= _gasLimit);
|
||||||
|
require(_nonce == nonce);
|
||||||
|
// calculates signHash
|
||||||
|
bytes32 signHash = getSignHash(
|
||||||
|
callGasRelayHash(
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
keccak256(_data),
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasLimit,
|
||||||
|
_gasToken
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
//verify if signatures are valid and came from correct actors;
|
||||||
|
verifySignatures(
|
||||||
|
_to == address(this) ? MANAGEMENT_KEY : ACTION_KEY,
|
||||||
|
signHash,
|
||||||
|
_messageSignatures
|
||||||
|
);
|
||||||
|
|
||||||
|
//executes transaction
|
||||||
|
nonce++;
|
||||||
|
bool success = _to.call.value(_value)(_data);
|
||||||
|
emit ExecutedGasRelayed(
|
||||||
|
signHash,
|
||||||
|
success
|
||||||
|
);
|
||||||
|
|
||||||
|
//refund gas used using contract held ERC20 tokens or ETH
|
||||||
|
if (_gasPrice > 0) {
|
||||||
|
uint256 _amount = 21000 + (startGas - gasleft());
|
||||||
|
_amount = _amount * _gasPrice;
|
||||||
|
if (_gasToken == address(0)) {
|
||||||
|
address(msg.sender).transfer(_amount);
|
||||||
|
} else {
|
||||||
|
ERC20Token(_gasToken).transfer(msg.sender, _amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice include ethereum signed approve ERC20 and call hash
|
||||||
|
* (`ERC20Token(baseToken).approve(_to, _value)` + `_to.call(_data)`).
|
||||||
|
* in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken`
|
||||||
|
* fixes race condition in double transaction for ERC20.
|
||||||
|
* @param _baseToken token approved for `_to`
|
||||||
|
* @param _to destination of call
|
||||||
|
* @param _value call value (ether)
|
||||||
|
* @param _data call data
|
||||||
|
* @param _nonce current identity nonce
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasLimit minimal gasLimit required to execute this call
|
||||||
|
* @param _gasToken token being used for paying `msg.sender`
|
||||||
|
* @param _messageSignatures rsv concatenated ethereum signed message signatures required
|
||||||
|
*/
|
||||||
|
function approveAndCallGasRelayed(
|
||||||
|
address _baseToken,
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data,
|
||||||
|
uint _nonce,
|
||||||
|
uint _gasPrice,
|
||||||
|
uint _gasLimit,
|
||||||
|
address _gasToken,
|
||||||
|
bytes _messageSignatures
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
uint startGas = gasleft();
|
||||||
|
//verify transaction parameters
|
||||||
|
require(startGas >= _gasLimit);
|
||||||
|
require(_nonce == nonce);
|
||||||
|
require(_baseToken != address(0)); //_baseToken should be something!
|
||||||
|
require(_to != address(this)); //no management with approveAndCall
|
||||||
|
|
||||||
|
// calculates signHash
|
||||||
|
bytes32 signHash = getSignHash(
|
||||||
|
approveAndCallGasRelayHash(
|
||||||
|
_baseToken,
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
keccak256(_data),
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasLimit,
|
||||||
|
_gasToken
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
//verify if signatures are valid and came from correct actors;
|
||||||
|
verifySignatures(
|
||||||
|
ACTION_KEY, //no management with approveAndCall
|
||||||
|
signHash,
|
||||||
|
_messageSignatures
|
||||||
|
);
|
||||||
|
|
||||||
|
approveAndCall(
|
||||||
|
signHash,
|
||||||
|
_baseToken,
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
_data
|
||||||
|
);
|
||||||
|
|
||||||
|
//refund gas used using contract held ERC20 tokens or ETH
|
||||||
|
if (_gasPrice > 0) {
|
||||||
|
uint256 _amount = 21000 + (startGas - gasleft());
|
||||||
|
_amount = _amount * _gasPrice;
|
||||||
|
if (_gasToken == address(0)) {
|
||||||
|
address(msg.sender).transfer(_amount);
|
||||||
|
} else {
|
||||||
|
ERC20Token(_gasToken).transfer(msg.sender, _amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice reverts if signatures are not valid for the signed hash and required key type.
|
||||||
|
* @param _requiredKey key required to call, if _to from payload is the identity itself, is `MANAGEMENT_KEY`, else `ACTION_KEY`
|
||||||
|
* @param _signHash ethereum signable callGasRelayHash message provided for the payload
|
||||||
|
* @param _messageSignatures ethereum signed `_signHash` messages
|
||||||
|
* @return true case valid
|
||||||
|
*/
|
||||||
|
function verifySignatures(
|
||||||
|
uint256 _requiredKey,
|
||||||
|
bytes32 _signHash,
|
||||||
|
bytes _messageSignatures
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns(bool)
|
||||||
|
{
|
||||||
|
uint _amountSignatures = _messageSignatures.length / 72;
|
||||||
|
require(_amountSignatures == purposeThreshold[_requiredKey]);
|
||||||
|
bytes32 _lastKey = 0;
|
||||||
|
for (uint256 i = 0; i < _amountSignatures; i++) {
|
||||||
|
bytes32 _currentKey = recoverKey(
|
||||||
|
_signHash,
|
||||||
|
_messageSignatures,
|
||||||
|
i
|
||||||
|
);
|
||||||
|
require(_currentKey > _lastKey); //assert keys are different
|
||||||
|
require(isKeyPurpose(_currentKey, _requiredKey));
|
||||||
|
_lastKey = _currentKey;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice get callHash
|
||||||
|
* @param _to destination of call
|
||||||
|
* @param _value call value (ether)
|
||||||
|
* @param _dataHash call data hash
|
||||||
|
* @param _nonce current identity nonce
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasLimit minimal gasLimit required to execute this call
|
||||||
|
* @param _gasToken token being used for paying `msg.sender`
|
||||||
|
* @return callGasRelayHash the hash to be signed by wallet
|
||||||
|
*/
|
||||||
|
function callGasRelayHash(
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes32 _dataHash,
|
||||||
|
uint _nonce,
|
||||||
|
uint256 _gasPrice,
|
||||||
|
uint256 _gasLimit,
|
||||||
|
address _gasToken
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 _callGasRelayHash)
|
||||||
|
{
|
||||||
|
_callGasRelayHash = keccak256(
|
||||||
|
address(this),
|
||||||
|
CALL_PREFIX,
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
_dataHash,
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasLimit,
|
||||||
|
_gasToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice get callHash
|
||||||
|
* @param _to destination of call
|
||||||
|
* @param _value call value (ether)
|
||||||
|
* @param _dataHash call data hash
|
||||||
|
* @param _nonce current identity nonce
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasLimit minimal gasLimit required to execute this call
|
||||||
|
* @param _gasToken token being used for paying `msg.sender`
|
||||||
|
* @return callGasRelayHash the hash to be signed by wallet
|
||||||
|
*/
|
||||||
|
function approveAndCallGasRelayHash(
|
||||||
|
address _baseToken,
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes32 _dataHash,
|
||||||
|
uint _nonce,
|
||||||
|
uint256 _gasPrice,
|
||||||
|
uint256 _gasLimit,
|
||||||
|
address _gasToken
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 _callGasRelayHash)
|
||||||
|
{
|
||||||
|
_callGasRelayHash = keccak256(
|
||||||
|
address(this),
|
||||||
|
APPROVEANDCALL_PREFIX,
|
||||||
|
_baseToken,
|
||||||
|
_to,
|
||||||
|
_value,
|
||||||
|
_dataHash,
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasLimit,
|
||||||
|
_gasToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"`
|
||||||
|
* @param _hash Sign to hash.
|
||||||
|
* @return signHash Hash ethereum wallet signs.
|
||||||
|
*/
|
||||||
|
function getSignHash(
|
||||||
|
bytes32 _hash
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns(bytes32 signHash)
|
||||||
|
{
|
||||||
|
signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice recovers address who signed the message
|
||||||
|
* @param _signHash operation ethereum signed message hash
|
||||||
|
* @param _messageSignature message `_signHash` signature
|
||||||
|
* @param _pos which signature to read
|
||||||
|
*/
|
||||||
|
function recoverKey (
|
||||||
|
bytes32 _signHash,
|
||||||
|
bytes _messageSignature,
|
||||||
|
uint256 _pos
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns(bytes32)
|
||||||
|
{
|
||||||
|
uint8 v;
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
(v,r,s) = signatureSplit(_messageSignature, _pos);
|
||||||
|
return keccak256(
|
||||||
|
ecrecover(
|
||||||
|
_signHash,
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
|
||||||
|
* @param _pos which signature to read
|
||||||
|
* @param _signatures concatenated vrs signatures
|
||||||
|
*/
|
||||||
|
function signatureSplit(bytes _signatures, uint256 _pos)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns (uint8 v, bytes32 r, bytes32 s)
|
||||||
|
{
|
||||||
|
uint pos = _pos + 1;
|
||||||
|
// The signature format is a compact form of:
|
||||||
|
// {bytes32 r}{bytes32 s}{uint8 v}
|
||||||
|
// Compact means, uint8 is not padded to 32 bytes.
|
||||||
|
assembly {
|
||||||
|
r := mload(add(_signatures, mul(32,pos)))
|
||||||
|
s := mload(add(_signatures, mul(64,pos)))
|
||||||
|
// Here we are loading the last 32 bytes, including 31 bytes
|
||||||
|
// of 's'. There is no 'mload8' to do this.
|
||||||
|
//
|
||||||
|
// 'byte' is not working due to the Solidity parser, so lets
|
||||||
|
// use the second best option, 'and'
|
||||||
|
v := and(mload(add(_signatures, mul(65,pos))), 0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
require(v == 27 || v == 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
function approveAndCall(
|
||||||
|
bytes32 _signHash,
|
||||||
|
address _token,
|
||||||
|
address _to,
|
||||||
|
uint256 _value,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
//executes transaction
|
||||||
|
nonce++;
|
||||||
|
ERC20Token(_token).approve(_to, _value);
|
||||||
|
emit ExecutedGasRelayed(
|
||||||
|
_signHash,
|
||||||
|
_to.call(_data)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
contracts/identity/IdentityKernel.sol
Normal file
11
contracts/identity/IdentityKernel.sol
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
298
contracts/status/SNTController.sol
Normal file
298
contracts/status/SNTController.sol
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
import "../token/TokenController.sol";
|
||||||
|
import "../common/Owned.sol";
|
||||||
|
import "../token/ERC20Token.sol";
|
||||||
|
import "../token/MiniMeToken.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title SNTController
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* @notice enables economic abstraction for SNT
|
||||||
|
*/
|
||||||
|
contract SNTController is TokenController, Owned {
|
||||||
|
|
||||||
|
|
||||||
|
bytes4 public constant TRANSFER_PREFIX = bytes4(keccak256("transferSNT(address,uint256,uint256,uint256)"));
|
||||||
|
bytes4 public constant EXECUTE_PREFIX = bytes4(keccak256("executeGasRelayed(address,bytes,uint256,uint256,uint256)"));
|
||||||
|
|
||||||
|
MiniMeToken public snt;
|
||||||
|
mapping (address => uint256) public signNonce;
|
||||||
|
mapping (address => bool) public allowPublicExecution;
|
||||||
|
|
||||||
|
event PublicExecutionEnabled(address indexed contractAddress, bool enabled);
|
||||||
|
event GasRelayedExecution(address indexed msgSigner, bytes32 signedHash, bool executed);
|
||||||
|
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
||||||
|
event ControllerChanged(address indexed _newController);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Constructor
|
||||||
|
* @param _owner Authority address
|
||||||
|
* @param _snt SNT token
|
||||||
|
*/
|
||||||
|
function SNTController(address _owner, address _snt) public {
|
||||||
|
owner = _owner;
|
||||||
|
snt = MiniMeToken(_snt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice allows externally owned address sign a message to transfer SNT and pay
|
||||||
|
* @param _to address receving the tokens from message signer
|
||||||
|
* @param _amount total being transfered
|
||||||
|
* @param _nonce current signNonce of message signer
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _signature concatenated rsv of message
|
||||||
|
*/
|
||||||
|
function transferSNT(
|
||||||
|
address _to,
|
||||||
|
uint256 _amount,
|
||||||
|
uint256 _nonce,
|
||||||
|
uint256 _gasPrice,
|
||||||
|
bytes _signature
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
uint256 startGas = gasleft();
|
||||||
|
bytes32 msgSigned = getSignHash(
|
||||||
|
getTransferSNTHash(
|
||||||
|
_to,
|
||||||
|
_amount,
|
||||||
|
_nonce,
|
||||||
|
_gasPrice
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
address msgSigner = recoverAddress(msgSigned, _signature);
|
||||||
|
require(signNonce[msgSigner] == _nonce);
|
||||||
|
signNonce[msgSigner]++;
|
||||||
|
if (snt.transferFrom(msgSigner, _to, _amount)) {
|
||||||
|
require(snt.transferFrom(msgSigner, msg.sender, (21000 + startGas-gasleft()) * _gasPrice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice allows externally owned address sign a message to offer SNT for a execution
|
||||||
|
* @param _allowedContract address of a contracts in execution trust list;
|
||||||
|
* @param _data msg.data to be sent to `_allowedContract`
|
||||||
|
* @param _nonce current signNonce of message signer
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasMinimal minimal amount of gas needed to complete the execution
|
||||||
|
* @param _signature concatenated rsv of message
|
||||||
|
*/
|
||||||
|
function executeGasRelayed(
|
||||||
|
address _allowedContract,
|
||||||
|
bytes _data,
|
||||||
|
uint256 _nonce,
|
||||||
|
uint256 _gasPrice,
|
||||||
|
uint256 _gasMinimal,
|
||||||
|
bytes _signature
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
uint256 startGas = gasleft();
|
||||||
|
require(startGas >= _gasMinimal);
|
||||||
|
bytes32 msgSigned = getSignHash(
|
||||||
|
getExecuteGasRelayedHash(
|
||||||
|
_allowedContract,
|
||||||
|
_data,
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasMinimal
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
address msgSigner = recoverAddress(msgSigned, _signature);
|
||||||
|
require(signNonce[msgSigner] == _nonce);
|
||||||
|
signNonce[msgSigner]++;
|
||||||
|
bool success = _allowedContract.call(_data);
|
||||||
|
emit GasRelayedExecution(msgSigner, msgSigned, success);
|
||||||
|
require(
|
||||||
|
snt.transferFrom(
|
||||||
|
msgSigner,
|
||||||
|
msg.sender,
|
||||||
|
(21000 + startGas-gasleft()) * _gasPrice
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice The owner of this contract can change the controller of the SNT token
|
||||||
|
* Please, be sure that the owner is a trusted agent or 0x0 address.
|
||||||
|
* @param _newController The address of the new controller
|
||||||
|
*/
|
||||||
|
function changeController(address _newController) public onlyOwner {
|
||||||
|
snt.changeController(_newController);
|
||||||
|
emit ControllerChanged(_newController);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enablePublicExecution(address _contract, bool _enable) public onlyOwner {
|
||||||
|
allowPublicExecution[_contract] = _enable;
|
||||||
|
emit PublicExecutionEnabled(_contract, _enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////
|
||||||
|
// Safety Methods
|
||||||
|
//////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice This method can be used by the controller to extract mistakenly
|
||||||
|
* sent tokens to this contract.
|
||||||
|
* @param _token The address of the token contract that you want to recover
|
||||||
|
* set to 0 in case you want to extract ether.
|
||||||
|
*/
|
||||||
|
function claimTokens(address _token) public onlyOwner {
|
||||||
|
if (snt.controller() == address(this)) {
|
||||||
|
snt.claimTokens(_token);
|
||||||
|
}
|
||||||
|
if (_token == 0x0) {
|
||||||
|
address(owner).transfer(this.balance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERC20Token token = ERC20Token(_token);
|
||||||
|
uint256 balance = token.balanceOf(this);
|
||||||
|
token.transfer(owner, balance);
|
||||||
|
emit ClaimedTokens(_token, owner, balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////
|
||||||
|
// MiniMe Controller Interface functions
|
||||||
|
//////////
|
||||||
|
|
||||||
|
// In between the offering and the network. Default settings for allowing token transfers.
|
||||||
|
function proxyPayment(address) public payable returns (bool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTransfer(address, address, uint256) public returns (bool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onApprove(address, address, uint256) public returns (bool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice get execution hash
|
||||||
|
* @param _allowedContract address of a contracts in execution trust list;
|
||||||
|
* @param _data msg.data to be sent to `_allowedContract`
|
||||||
|
* @param _nonce current signNonce of message signer
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
* @param _gasMinimal minimal amount of gas needed to complete the execution
|
||||||
|
*/
|
||||||
|
function getExecuteGasRelayedHash(
|
||||||
|
address _allowedContract,
|
||||||
|
bytes _data,
|
||||||
|
uint256 _nonce,
|
||||||
|
uint256 _gasPrice,
|
||||||
|
uint256 _gasMinimal
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 execHash)
|
||||||
|
{
|
||||||
|
execHash = keccak256(
|
||||||
|
address(this),
|
||||||
|
EXECUTE_PREFIX,
|
||||||
|
_allowedContract,
|
||||||
|
keccak256(_data),
|
||||||
|
_nonce,
|
||||||
|
_gasPrice,
|
||||||
|
_gasMinimal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice get transfer hash
|
||||||
|
* @param _to address receving the tokens from message signer
|
||||||
|
* @param _amount total being transfered
|
||||||
|
* @param _nonce current signNonce of message signer
|
||||||
|
* @param _gasPrice price in SNT paid back to msg.sender for each gas unit used
|
||||||
|
*/
|
||||||
|
function getTransferSNTHash(
|
||||||
|
address _to,
|
||||||
|
uint256 _amount,
|
||||||
|
uint256 _nonce,
|
||||||
|
uint256 _gasPrice
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 txHash)
|
||||||
|
{
|
||||||
|
txHash = keccak256(
|
||||||
|
address(this),
|
||||||
|
TRANSFER_PREFIX,
|
||||||
|
_to,
|
||||||
|
_amount,
|
||||||
|
_nonce,
|
||||||
|
_gasPrice
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice recovers address who signed the message
|
||||||
|
* @param _signHash operation ethereum signed message hash
|
||||||
|
* @param _messageSignature message `_signHash` signature
|
||||||
|
*/
|
||||||
|
function recoverAddress(
|
||||||
|
bytes32 _signHash,
|
||||||
|
bytes _messageSignature
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns(address)
|
||||||
|
{
|
||||||
|
uint8 v;
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
(v,r,s) = signatureSplit(_messageSignature);
|
||||||
|
return ecrecover(
|
||||||
|
_signHash,
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
|
||||||
|
*/
|
||||||
|
function signatureSplit(bytes _signature)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns (uint8 v, bytes32 r, bytes32 s)
|
||||||
|
{
|
||||||
|
// The signature format is a compact form of:
|
||||||
|
// {bytes32 r}{bytes32 s}{uint8 v}
|
||||||
|
// Compact means, uint8 is not padded to 32 bytes.
|
||||||
|
assembly {
|
||||||
|
r := mload(add(_signature, 32))
|
||||||
|
s := mload(add(_signature, 64))
|
||||||
|
// Here we are loading the last 32 bytes, including 31 bytes
|
||||||
|
// of 's'. There is no 'mload8' to do this.
|
||||||
|
//
|
||||||
|
// 'byte' is not working due to the Solidity parser, so lets
|
||||||
|
// use the second best option, 'and'
|
||||||
|
v := and(mload(add(_signature, 65)), 0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
require(v == 27 || v == 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"`
|
||||||
|
* @param _hash Sign to hash.
|
||||||
|
* @return signHash Hash to be signed.
|
||||||
|
*/
|
||||||
|
function getSignHash(
|
||||||
|
bytes32 _hash
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
public
|
||||||
|
returns (bytes32 signHash)
|
||||||
|
{
|
||||||
|
signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
contracts/tests/TestContract.sol
Normal file
33
contracts/tests/TestContract.sol
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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)
|
||||||
|
public
|
||||||
|
pure
|
||||||
|
returns(bytes32)
|
||||||
|
{
|
||||||
|
return keccak256(identity, _revealedSecret, _dest, _data, _newSecret, _newFriendsHashes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
contracts/tests/UpdatedIdentityKernel.sol
Normal file
13
contracts/tests/UpdatedIdentityKernel.sol
Normal file
@ -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(purposeThreshold[MANAGEMENT_KEY]);
|
||||||
|
}
|
||||||
|
}
|
5
contracts/token/ApproveAndCallFallBack.sol
Normal file
5
contracts/token/ApproveAndCallFallBack.sol
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pragma solidity ^0.4.14;
|
||||||
|
|
||||||
|
contract ApproveAndCallFallBack {
|
||||||
|
function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public ;
|
||||||
|
}
|
582
contracts/token/MiniMeToken.sol
Normal file
582
contracts/token/MiniMeToken.sol
Normal file
@ -0,0 +1,582 @@
|
|||||||
|
pragma solidity ^0.4.6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2016, Jordi Baylina
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// @title MiniMeToken Contract
|
||||||
|
/// @author Jordi Baylina
|
||||||
|
/// @dev This token contract's goal is to make it easy for anyone to clone this
|
||||||
|
/// token using the token distribution at a given block, this will allow DAO's
|
||||||
|
/// and DApps to upgrade their features in a decentralized manner without
|
||||||
|
/// affecting the original token
|
||||||
|
/// @dev It is ERC20 compliant, but still needs to under go further testing.
|
||||||
|
|
||||||
|
import "../common/Controlled.sol";
|
||||||
|
import "./TokenController.sol";
|
||||||
|
import "./ApproveAndCallFallBack.sol";
|
||||||
|
import "./MiniMeTokenInterface.sol";
|
||||||
|
import "./MiniMeTokenFactory.sol";
|
||||||
|
|
||||||
|
/// @dev The actual token contract, the default controller is the msg.sender
|
||||||
|
/// that deploys the contract, so usually this token will be deployed by a
|
||||||
|
/// token controller contract, which Giveth will call a "Campaign"
|
||||||
|
contract MiniMeToken is MiniMeTokenInterface, Controlled {
|
||||||
|
|
||||||
|
string public name; //The Token's name: e.g. DigixDAO Tokens
|
||||||
|
uint8 public decimals; //Number of decimals of the smallest unit
|
||||||
|
string public symbol; //An identifier: e.g. REP
|
||||||
|
string public version = "MMT_0.1"; //An arbitrary versioning scheme
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev `Checkpoint` is the structure that attaches a block number to a
|
||||||
|
/// given value, the block number attached is the one that last changed the
|
||||||
|
/// value
|
||||||
|
struct Checkpoint {
|
||||||
|
|
||||||
|
// `fromBlock` is the block number that the value was generated from
|
||||||
|
uint128 fromBlock;
|
||||||
|
|
||||||
|
// `value` is the amount of tokens at a specific block number
|
||||||
|
uint128 value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `parentToken` is the Token address that was cloned to produce this token;
|
||||||
|
// it will be 0x0 for a token that was not cloned
|
||||||
|
MiniMeToken public parentToken;
|
||||||
|
|
||||||
|
// `parentSnapShotBlock` is the block number from the Parent Token that was
|
||||||
|
// used to determine the initial distribution of the Clone Token
|
||||||
|
uint public parentSnapShotBlock;
|
||||||
|
|
||||||
|
// `creationBlock` is the block number that the Clone Token was created
|
||||||
|
uint public creationBlock;
|
||||||
|
|
||||||
|
// `balances` is the map that tracks the balance of each address, in this
|
||||||
|
// contract when the balance changes the block number that the change
|
||||||
|
// occurred is also included in the map
|
||||||
|
mapping (address => Checkpoint[]) balances;
|
||||||
|
|
||||||
|
// `allowed` tracks any extra transfer rights as in all ERC20 tokens
|
||||||
|
mapping (address => mapping (address => uint256)) allowed;
|
||||||
|
|
||||||
|
// Tracks the history of the `totalSupply` of the token
|
||||||
|
Checkpoint[] totalSupplyHistory;
|
||||||
|
|
||||||
|
// Flag that determines if the token is transferable or not.
|
||||||
|
bool public transfersEnabled;
|
||||||
|
|
||||||
|
// The factory used to create new clone tokens
|
||||||
|
MiniMeTokenFactory public tokenFactory;
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Constructor
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @notice Constructor to create a MiniMeToken
|
||||||
|
/// @param _tokenFactory The address of the MiniMeTokenFactory contract that
|
||||||
|
/// will create the Clone token contracts, the token factory needs to be
|
||||||
|
/// deployed first
|
||||||
|
/// @param _parentToken Address of the parent token, set to 0x0 if it is a
|
||||||
|
/// new token
|
||||||
|
/// @param _parentSnapShotBlock Block of the parent token that will
|
||||||
|
/// determine the initial distribution of the clone token, set to 0 if it
|
||||||
|
/// is a new token
|
||||||
|
/// @param _tokenName Name of the new token
|
||||||
|
/// @param _decimalUnits Number of decimals of the new token
|
||||||
|
/// @param _tokenSymbol Token Symbol for the new token
|
||||||
|
/// @param _transfersEnabled If true, tokens will be able to be transferred
|
||||||
|
function MiniMeToken(
|
||||||
|
address _tokenFactory,
|
||||||
|
address _parentToken,
|
||||||
|
uint _parentSnapShotBlock,
|
||||||
|
string _tokenName,
|
||||||
|
uint8 _decimalUnits,
|
||||||
|
string _tokenSymbol,
|
||||||
|
bool _transfersEnabled
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
tokenFactory = MiniMeTokenFactory(_tokenFactory);
|
||||||
|
name = _tokenName; // Set the name
|
||||||
|
decimals = _decimalUnits; // Set the decimals
|
||||||
|
symbol = _tokenSymbol; // Set the symbol
|
||||||
|
parentToken = MiniMeToken(_parentToken);
|
||||||
|
parentSnapShotBlock = _parentSnapShotBlock;
|
||||||
|
transfersEnabled = _transfersEnabled;
|
||||||
|
creationBlock = block.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// ERC20 Methods
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
/// @notice Send `_amount` tokens to `_to` from `msg.sender`
|
||||||
|
/// @param _to The address of the recipient
|
||||||
|
/// @param _amount The amount of tokens to be transferred
|
||||||
|
/// @return Whether the transfer was successful or not
|
||||||
|
function transfer(address _to, uint256 _amount) public returns (bool success) {
|
||||||
|
require(transfersEnabled);
|
||||||
|
return doTransfer(msg.sender, _to, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Send `_amount` tokens to `_to` from `_from` on the condition it
|
||||||
|
/// is approved by `_from`
|
||||||
|
/// @param _from The address holding the tokens being transferred
|
||||||
|
/// @param _to The address of the recipient
|
||||||
|
/// @param _amount The amount of tokens to be transferred
|
||||||
|
/// @return True if the transfer was successful
|
||||||
|
function transferFrom(
|
||||||
|
address _from,
|
||||||
|
address _to,
|
||||||
|
uint256 _amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
|
||||||
|
// The controller of this contract can move tokens around at will,
|
||||||
|
// this is important to recognize! Confirm that you trust the
|
||||||
|
// controller of this contract, which in most situations should be
|
||||||
|
// another open source smart contract or 0x0
|
||||||
|
if (msg.sender != controller) {
|
||||||
|
require(transfersEnabled);
|
||||||
|
|
||||||
|
// The standard ERC 20 transferFrom functionality
|
||||||
|
if (allowed[_from][msg.sender] < _amount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
allowed[_from][msg.sender] -= _amount;
|
||||||
|
}
|
||||||
|
return doTransfer(_from, _to, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev This is the actual transfer function in the token contract, it can
|
||||||
|
/// only be called by other functions in this contract.
|
||||||
|
/// @param _from The address holding the tokens being transferred
|
||||||
|
/// @param _to The address of the recipient
|
||||||
|
/// @param _amount The amount of tokens to be transferred
|
||||||
|
/// @return True if the transfer was successful
|
||||||
|
function doTransfer(
|
||||||
|
address _from,
|
||||||
|
address _to,
|
||||||
|
uint _amount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
returns(bool)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_amount == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
require(parentSnapShotBlock < block.number);
|
||||||
|
|
||||||
|
// Do not allow transfer to 0x0 or the token contract itself
|
||||||
|
require((_to != 0) && (_to != address(this)));
|
||||||
|
|
||||||
|
// If the amount being transfered is more than the balance of the
|
||||||
|
// account the transfer returns false
|
||||||
|
var previousBalanceFrom = balanceOfAt(_from, block.number);
|
||||||
|
if (previousBalanceFrom < _amount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alerts the token controller of the transfer
|
||||||
|
if (isContract(controller)) {
|
||||||
|
require(TokenController(controller).onTransfer(_from, _to, _amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// First update the balance array with the new value for the address
|
||||||
|
// sending the tokens
|
||||||
|
updateValueAtNow(balances[_from], previousBalanceFrom - _amount);
|
||||||
|
|
||||||
|
// Then update the balance array with the new value for the address
|
||||||
|
// receiving the tokens
|
||||||
|
var previousBalanceTo = balanceOfAt(_to, block.number);
|
||||||
|
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
|
||||||
|
updateValueAtNow(balances[_to], previousBalanceTo + _amount);
|
||||||
|
|
||||||
|
// An event to make the transfer easy to find on the blockchain
|
||||||
|
Transfer(_from, _to, _amount);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @param _owner The address that's balance is being requested
|
||||||
|
/// @return The balance of `_owner` at the current block
|
||||||
|
function balanceOf(address _owner) public constant returns (uint256 balance) {
|
||||||
|
return balanceOfAt(_owner, block.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
|
||||||
|
/// its behalf. This is a modified version of the ERC20 approve function
|
||||||
|
/// to be a little bit safer
|
||||||
|
/// @param _spender The address of the account able to transfer the tokens
|
||||||
|
/// @param _amount The amount of tokens to be approved for transfer
|
||||||
|
/// @return True if the approval was successful
|
||||||
|
function approve(address _spender, uint256 _amount) public returns (bool success) {
|
||||||
|
require(transfersEnabled);
|
||||||
|
|
||||||
|
// To change the approve amount you first have to reduce the addresses`
|
||||||
|
// allowance to zero by calling `approve(_spender,0)` if it is not
|
||||||
|
// already 0 to mitigate the race condition described here:
|
||||||
|
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
|
||||||
|
require((_amount == 0) || (allowed[msg.sender][_spender] == 0));
|
||||||
|
|
||||||
|
// Alerts the token controller of the approve function call
|
||||||
|
if (isContract(controller)) {
|
||||||
|
require(TokenController(controller).onApprove(msg.sender, _spender, _amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed[msg.sender][_spender] = _amount;
|
||||||
|
Approval(msg.sender, _spender, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev This function makes it easy to read the `allowed[]` map
|
||||||
|
/// @param _owner The address of the account that owns the token
|
||||||
|
/// @param _spender The address of the account able to transfer the tokens
|
||||||
|
/// @return Amount of remaining tokens of _owner that _spender is allowed
|
||||||
|
/// to spend
|
||||||
|
function allowance(
|
||||||
|
address _owner,
|
||||||
|
address _spender
|
||||||
|
)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint256 remaining)
|
||||||
|
{
|
||||||
|
return allowed[_owner][_spender];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice `msg.sender` approves `_spender` to send `_amount` tokens on
|
||||||
|
/// its behalf, and then a function is triggered in the contract that is
|
||||||
|
/// being approved, `_spender`. This allows users to use their tokens to
|
||||||
|
/// interact with contracts in one function call instead of two
|
||||||
|
/// @param _spender The address of the contract able to transfer the tokens
|
||||||
|
/// @param _amount The amount of tokens to be approved for transfer
|
||||||
|
/// @return True if the function call was successful
|
||||||
|
function approveAndCall(
|
||||||
|
address _spender,
|
||||||
|
uint256 _amount,
|
||||||
|
bytes _extraData
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bool success)
|
||||||
|
{
|
||||||
|
require(approve(_spender, _amount));
|
||||||
|
|
||||||
|
ApproveAndCallFallBack(_spender).receiveApproval(
|
||||||
|
msg.sender,
|
||||||
|
_amount,
|
||||||
|
this,
|
||||||
|
_extraData
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev This function makes it easy to get the total number of tokens
|
||||||
|
/// @return The total number of tokens
|
||||||
|
function totalSupply() public constant returns (uint) {
|
||||||
|
return totalSupplyAt(block.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Query balance and totalSupply in History
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @dev Queries the balance of `_owner` at a specific `_blockNumber`
|
||||||
|
/// @param _owner The address from which the balance will be retrieved
|
||||||
|
/// @param _blockNumber The block number when the balance is queried
|
||||||
|
/// @return The balance at `_blockNumber`
|
||||||
|
function balanceOfAt(
|
||||||
|
address _owner,
|
||||||
|
uint _blockNumber
|
||||||
|
)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint)
|
||||||
|
{
|
||||||
|
|
||||||
|
// These next few lines are used when the balance of the token is
|
||||||
|
// requested before a check point was ever created for this token, it
|
||||||
|
// requires that the `parentToken.balanceOfAt` be queried at the
|
||||||
|
// genesis block for that token as this contains initial balance of
|
||||||
|
// this token
|
||||||
|
if ((balances[_owner].length == 0)
|
||||||
|
|| (balances[_owner][0].fromBlock > _blockNumber)) {
|
||||||
|
if (address(parentToken) != 0) {
|
||||||
|
return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
|
||||||
|
} else {
|
||||||
|
// Has no parent
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will return the expected balance during normal situations
|
||||||
|
} else {
|
||||||
|
return getValueAt(balances[_owner], _blockNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Total amount of tokens at a specific `_blockNumber`.
|
||||||
|
/// @param _blockNumber The block number when the totalSupply is queried
|
||||||
|
/// @return The total amount of tokens at `_blockNumber`
|
||||||
|
function totalSupplyAt(uint _blockNumber) public constant returns(uint) {
|
||||||
|
|
||||||
|
// These next few lines are used when the totalSupply of the token is
|
||||||
|
// requested before a check point was ever created for this token, it
|
||||||
|
// requires that the `parentToken.totalSupplyAt` be queried at the
|
||||||
|
// genesis block for this token as that contains totalSupply of this
|
||||||
|
// token at this block number.
|
||||||
|
if ((totalSupplyHistory.length == 0)
|
||||||
|
|| (totalSupplyHistory[0].fromBlock > _blockNumber)) {
|
||||||
|
if (address(parentToken) != 0) {
|
||||||
|
return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will return the expected totalSupply during normal situations
|
||||||
|
} else {
|
||||||
|
return getValueAt(totalSupplyHistory, _blockNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Clone Token Method
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @notice Creates a new clone token with the initial distribution being
|
||||||
|
/// this token at `_snapshotBlock`
|
||||||
|
/// @param _cloneTokenName Name of the clone token
|
||||||
|
/// @param _cloneDecimalUnits Number of decimals of the smallest unit
|
||||||
|
/// @param _cloneTokenSymbol Symbol of the clone token
|
||||||
|
/// @param _snapshotBlock Block when the distribution of the parent token is
|
||||||
|
/// copied to set the initial distribution of the new clone token;
|
||||||
|
/// if the block is zero than the actual block, the current block is used
|
||||||
|
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||||
|
/// @return The address of the new MiniMeToken Contract
|
||||||
|
function createCloneToken(
|
||||||
|
string _cloneTokenName,
|
||||||
|
uint8 _cloneDecimalUnits,
|
||||||
|
string _cloneTokenSymbol,
|
||||||
|
uint _snapshotBlock,
|
||||||
|
bool _transfersEnabled
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns(address)
|
||||||
|
{
|
||||||
|
if (_snapshotBlock == 0) {
|
||||||
|
_snapshotBlock = block.number;
|
||||||
|
}
|
||||||
|
MiniMeToken cloneToken = tokenFactory.createCloneToken(
|
||||||
|
this,
|
||||||
|
_snapshotBlock,
|
||||||
|
_cloneTokenName,
|
||||||
|
_cloneDecimalUnits,
|
||||||
|
_cloneTokenSymbol,
|
||||||
|
_transfersEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
cloneToken.changeController(msg.sender);
|
||||||
|
|
||||||
|
// An event to make the token easy to find on the blockchain
|
||||||
|
NewCloneToken(address(cloneToken), _snapshotBlock);
|
||||||
|
return address(cloneToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Generate and destroy tokens
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @notice Generates `_amount` tokens that are assigned to `_owner`
|
||||||
|
/// @param _owner The address that will be assigned the new tokens
|
||||||
|
/// @param _amount The quantity of tokens generated
|
||||||
|
/// @return True if the tokens are generated correctly
|
||||||
|
function generateTokens(
|
||||||
|
address _owner,
|
||||||
|
uint _amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
onlyController
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
uint curTotalSupply = totalSupply();
|
||||||
|
require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow
|
||||||
|
uint previousBalanceTo = balanceOf(_owner);
|
||||||
|
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
|
||||||
|
updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
|
||||||
|
updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
|
||||||
|
Transfer(0, _owner, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @notice Burns `_amount` tokens from `_owner`
|
||||||
|
/// @param _owner The address that will lose the tokens
|
||||||
|
/// @param _amount The quantity of tokens to burn
|
||||||
|
/// @return True if the tokens are burned correctly
|
||||||
|
function destroyTokens(
|
||||||
|
address _owner,
|
||||||
|
uint _amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
onlyController
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
uint curTotalSupply = totalSupply();
|
||||||
|
require(curTotalSupply >= _amount);
|
||||||
|
uint previousBalanceFrom = balanceOf(_owner);
|
||||||
|
require(previousBalanceFrom >= _amount);
|
||||||
|
updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
|
||||||
|
updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
|
||||||
|
Transfer(_owner, 0, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Enable tokens transfers
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/// @notice Enables token holders to transfer their tokens freely if true
|
||||||
|
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||||
|
function enableTransfers(bool _transfersEnabled) public onlyController {
|
||||||
|
transfersEnabled = _transfersEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Internal helper functions to query and set a value in a snapshot array
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @dev `getValueAt` retrieves the number of tokens at a given block number
|
||||||
|
/// @param checkpoints The history of values being queried
|
||||||
|
/// @param _block The block number to retrieve the value at
|
||||||
|
/// @return The number of tokens being queried
|
||||||
|
function getValueAt(
|
||||||
|
Checkpoint[] storage checkpoints,
|
||||||
|
uint _block
|
||||||
|
)
|
||||||
|
constant
|
||||||
|
internal
|
||||||
|
returns (uint)
|
||||||
|
{
|
||||||
|
if (checkpoints.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcut for the actual value
|
||||||
|
if (_block >= checkpoints[checkpoints.length-1].fromBlock) {
|
||||||
|
return checkpoints[checkpoints.length-1].value;
|
||||||
|
}
|
||||||
|
if (_block < checkpoints[0].fromBlock) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search of the value in the array
|
||||||
|
uint min = 0;
|
||||||
|
uint max = checkpoints.length-1;
|
||||||
|
while (max > min) {
|
||||||
|
uint mid = (max + min + 1) / 2;
|
||||||
|
if (checkpoints[mid].fromBlock<=_block) {
|
||||||
|
min = mid;
|
||||||
|
} else {
|
||||||
|
max = mid-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkpoints[min].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev `updateValueAtNow` used to update the `balances` map and the
|
||||||
|
/// `totalSupplyHistory`
|
||||||
|
/// @param checkpoints The history of data being updated
|
||||||
|
/// @param _value The new number of tokens
|
||||||
|
function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal {
|
||||||
|
if ((checkpoints.length == 0)
|
||||||
|
|| (checkpoints[checkpoints.length -1].fromBlock < block.number)) {
|
||||||
|
Checkpoint storage newCheckPoint = checkpoints[ checkpoints.length++ ];
|
||||||
|
newCheckPoint.fromBlock = uint128(block.number);
|
||||||
|
newCheckPoint.value = uint128(_value);
|
||||||
|
} else {
|
||||||
|
Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
|
||||||
|
oldCheckPoint.value = uint128(_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Internal function to determine if an address is a contract
|
||||||
|
/// @param _addr The address being queried
|
||||||
|
/// @return True if `_addr` is a contract
|
||||||
|
function isContract(address _addr) constant internal returns(bool) {
|
||||||
|
uint size;
|
||||||
|
if (_addr == 0){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assembly {
|
||||||
|
size := extcodesize(_addr)
|
||||||
|
}
|
||||||
|
return size>0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Helper function to return a min betwen the two uints
|
||||||
|
function min(uint a, uint b) internal returns (uint) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice The fallback function: If the contract's controller has not been
|
||||||
|
/// set to 0, then the `proxyPayment` method is called which relays the
|
||||||
|
/// ether and creates tokens as described in the token controller contract
|
||||||
|
function () public payable {
|
||||||
|
require(isContract(controller));
|
||||||
|
require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////
|
||||||
|
// Safety Methods
|
||||||
|
//////////
|
||||||
|
|
||||||
|
/// @notice This method can be used by the controller to extract mistakenly
|
||||||
|
/// sent tokens to this contract.
|
||||||
|
/// @param _token The address of the token contract that you want to recover
|
||||||
|
/// set to 0 in case you want to extract ether.
|
||||||
|
function claimTokens(address _token) public onlyController {
|
||||||
|
if (_token == 0x0) {
|
||||||
|
controller.transfer(this.balance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MiniMeToken token = MiniMeToken(_token);
|
||||||
|
uint balance = token.balanceOf(this);
|
||||||
|
token.transfer(controller, balance);
|
||||||
|
ClaimedTokens(_token, controller, balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Events
|
||||||
|
////////////////
|
||||||
|
event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount);
|
||||||
|
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
|
||||||
|
event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock);
|
||||||
|
event Approval(
|
||||||
|
address indexed _owner,
|
||||||
|
address indexed _spender,
|
||||||
|
uint256 _amount
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
45
contracts/token/MiniMeTokenFactory.sol
Normal file
45
contracts/token/MiniMeTokenFactory.sol
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
pragma solidity ^0.4.11;
|
||||||
|
|
||||||
|
import "./MiniMeToken.sol";
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// MiniMeTokenFactory
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// @dev This contract is used to generate clone contracts from a contract.
|
||||||
|
/// In solidity this is the way to create a contract from a contract of the
|
||||||
|
/// same class
|
||||||
|
contract MiniMeTokenFactory {
|
||||||
|
|
||||||
|
/// @notice Update the DApp by creating a new token with new functionalities
|
||||||
|
/// the msg.sender becomes the controller of this clone token
|
||||||
|
/// @param _parentToken Address of the token being cloned
|
||||||
|
/// @param _snapshotBlock Block of the parent token that will
|
||||||
|
/// determine the initial distribution of the clone token
|
||||||
|
/// @param _tokenName Name of the new token
|
||||||
|
/// @param _decimalUnits Number of decimals of the new token
|
||||||
|
/// @param _tokenSymbol Token Symbol for the new token
|
||||||
|
/// @param _transfersEnabled If true, tokens will be able to be transferred
|
||||||
|
/// @return The address of the new token contract
|
||||||
|
function createCloneToken(
|
||||||
|
address _parentToken,
|
||||||
|
uint _snapshotBlock,
|
||||||
|
string _tokenName,
|
||||||
|
uint8 _decimalUnits,
|
||||||
|
string _tokenSymbol,
|
||||||
|
bool _transfersEnabled
|
||||||
|
) public returns (MiniMeToken) {
|
||||||
|
MiniMeToken newToken = new MiniMeToken(
|
||||||
|
this,
|
||||||
|
_parentToken,
|
||||||
|
_snapshotBlock,
|
||||||
|
_tokenName,
|
||||||
|
_decimalUnits,
|
||||||
|
_tokenSymbol,
|
||||||
|
_transfersEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
newToken.changeController(msg.sender);
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
}
|
129
contracts/token/MiniMeTokenInterface.sol
Normal file
129
contracts/token/MiniMeTokenInterface.sol
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
pragma solidity ^0.4.17;
|
||||||
|
|
||||||
|
contract MiniMeTokenInterface {
|
||||||
|
|
||||||
|
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||||
|
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
|
||||||
|
|
||||||
|
/// @dev This function makes it easy to get the total number of tokens
|
||||||
|
/// @return The total number of tokens
|
||||||
|
function totalSupply() public constant returns (uint);
|
||||||
|
|
||||||
|
/// @param _owner The address from which the balance will be retrieved
|
||||||
|
/// @return The balance
|
||||||
|
function balanceOf(address _owner) public constant returns (uint256 balance);
|
||||||
|
|
||||||
|
/// @notice send `_value` token to `_to` from `msg.sender`
|
||||||
|
/// @param _to The address of the recipient
|
||||||
|
/// @param _value The amount of token to be transferred
|
||||||
|
/// @return Whether the transfer was successful or not
|
||||||
|
function transfer(address _to, uint256 _value) public returns (bool success);
|
||||||
|
|
||||||
|
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
|
||||||
|
/// @param _from The address of the sender
|
||||||
|
/// @param _to The address of the recipient
|
||||||
|
/// @param _value The amount of token to be transferred
|
||||||
|
/// @return Whether the transfer was successful or not
|
||||||
|
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
|
||||||
|
|
||||||
|
/// @notice `msg.sender` approves `_spender` to spend `_value` tokens
|
||||||
|
/// @param _spender The address of the account able to transfer the tokens
|
||||||
|
/// @param _value The amount of tokens to be approved for transfer
|
||||||
|
/// @return Whether the approval was successful or not
|
||||||
|
function approve(address _spender, uint256 _value) public returns (bool success);
|
||||||
|
|
||||||
|
/// @param _owner The address of the account owning tokens
|
||||||
|
/// @param _spender The address of the account able to transfer the tokens
|
||||||
|
/// @return Amount of remaining tokens allowed to spent
|
||||||
|
function allowance(address _owner, address _spender) public constant returns (uint256 remaining);
|
||||||
|
|
||||||
|
/// @notice `msg.sender` approves `_spender` to send `_amount` tokens on
|
||||||
|
/// its behalf, and then a function is triggered in the contract that is
|
||||||
|
/// being approved, `_spender`. This allows users to use their tokens to
|
||||||
|
/// interact with contracts in one function call instead of two
|
||||||
|
/// @param _spender The address of the contract able to transfer the tokens
|
||||||
|
/// @param _amount The amount of tokens to be approved for transfer
|
||||||
|
/// @return True if the function call was successful
|
||||||
|
function approveAndCall(
|
||||||
|
address _spender,
|
||||||
|
uint256 _amount,
|
||||||
|
bytes _extraData
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bool success);
|
||||||
|
|
||||||
|
|
||||||
|
/// @notice Creates a new clone token with the initial distribution being
|
||||||
|
/// this token at `_snapshotBlock`
|
||||||
|
/// @param _cloneTokenName Name of the clone token
|
||||||
|
/// @param _cloneDecimalUnits Number of decimals of the smallest unit
|
||||||
|
/// @param _cloneTokenSymbol Symbol of the clone token
|
||||||
|
/// @param _snapshotBlock Block when the distribution of the parent token is
|
||||||
|
/// copied to set the initial distribution of the new clone token;
|
||||||
|
/// if the block is zero than the actual block, the current block is used
|
||||||
|
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||||
|
/// @return The address of the new MiniMeToken Contract
|
||||||
|
function createCloneToken(
|
||||||
|
string _cloneTokenName,
|
||||||
|
uint8 _cloneDecimalUnits,
|
||||||
|
string _cloneTokenSymbol,
|
||||||
|
uint _snapshotBlock,
|
||||||
|
bool _transfersEnabled
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns(address);
|
||||||
|
|
||||||
|
/// @notice Generates `_amount` tokens that are assigned to `_owner`
|
||||||
|
/// @param _owner The address that will be assigned the new tokens
|
||||||
|
/// @param _amount The quantity of tokens generated
|
||||||
|
/// @return True if the tokens are generated correctly
|
||||||
|
function generateTokens(
|
||||||
|
address _owner,
|
||||||
|
uint _amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bool);
|
||||||
|
|
||||||
|
/// @notice Burns `_amount` tokens from `_owner`
|
||||||
|
/// @param _owner The address that will lose the tokens
|
||||||
|
/// @param _amount The quantity of tokens to burn
|
||||||
|
/// @return True if the tokens are burned correctly
|
||||||
|
function destroyTokens(
|
||||||
|
address _owner,
|
||||||
|
uint _amount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (bool);
|
||||||
|
|
||||||
|
|
||||||
|
/// @notice Enables token holders to transfer their tokens freely if true
|
||||||
|
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||||
|
function enableTransfers(bool _transfersEnabled) public;
|
||||||
|
|
||||||
|
|
||||||
|
/// @notice This method can be used by the controller to extract mistakenly
|
||||||
|
/// sent tokens to this contract.
|
||||||
|
/// @param _token The address of the token contract that you want to recover
|
||||||
|
/// set to 0 in case you want to extract ether.
|
||||||
|
function claimTokens(address _token) public;
|
||||||
|
|
||||||
|
/// @dev Queries the balance of `_owner` at a specific `_blockNumber`
|
||||||
|
/// @param _owner The address from which the balance will be retrieved
|
||||||
|
/// @param _blockNumber The block number when the balance is queried
|
||||||
|
/// @return The balance at `_blockNumber`
|
||||||
|
function balanceOfAt(
|
||||||
|
address _owner,
|
||||||
|
uint _blockNumber
|
||||||
|
)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint);
|
||||||
|
|
||||||
|
/// @notice Total amount of tokens at a specific `_blockNumber`.
|
||||||
|
/// @param _blockNumber The block number when the totalSupply is queried
|
||||||
|
/// @return The total amount of tokens at `_blockNumber`
|
||||||
|
function totalSupplyAt(uint _blockNumber) public constant returns(uint);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
26
contracts/token/TokenController.sol
Normal file
26
contracts/token/TokenController.sol
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pragma solidity ^0.4.14;
|
||||||
|
|
||||||
|
/// @dev The token controller contract must implement these functions
|
||||||
|
contract TokenController {
|
||||||
|
/// @notice Called when `_owner` sends ether to the MiniMe Token contract
|
||||||
|
/// @param _owner The address that sent the ether to create tokens
|
||||||
|
/// @return True if the ether is accepted, false if it throws
|
||||||
|
function proxyPayment(address _owner) payable returns(bool);
|
||||||
|
|
||||||
|
/// @notice Notifies the controller about a token transfer allowing the
|
||||||
|
/// controller to react if desired
|
||||||
|
/// @param _from The origin of the transfer
|
||||||
|
/// @param _to The destination of the transfer
|
||||||
|
/// @param _amount The amount of the transfer
|
||||||
|
/// @return False if the controller does not authorize the transfer
|
||||||
|
function onTransfer(address _from, address _to, uint _amount) returns(bool);
|
||||||
|
|
||||||
|
/// @notice Notifies the controller about an approval allowing the
|
||||||
|
/// controller to react if desired
|
||||||
|
/// @param _owner The address that calls `approve()`
|
||||||
|
/// @param _spender The spender in the `approve()` call
|
||||||
|
/// @param _amount The amount in the `approve()` call
|
||||||
|
/// @return False if the controller does not authorize the approval
|
||||||
|
function onApprove(address _owner, address _spender, uint _amount)
|
||||||
|
returns(bool);
|
||||||
|
}
|
144
docs/Identity.md
Normal file
144
docs/Identity.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Identity contracts
|
||||||
|
|
||||||
|
# Table of content
|
||||||
|
- [Summary](#summary)
|
||||||
|
- [Smart Contracts Overview](#smart-contracts-overview)
|
||||||
|
- [Tutorials](#tutorials)
|
||||||
|
- - [Identity Creation](#identity-creation)
|
||||||
|
- - [Executions and Approvals](#executions-and-approvals)
|
||||||
|
- - [Management Activities](#management-activities)
|
||||||
|
- - [Constants / View functions](#constants--view-functions)
|
||||||
|
- - [Adding new functionality to identitities](#adding-new-functionality-to-identitities)
|
||||||
|
- - [Upgrade an `IdentityKernel` instance](#upgrade-an-identitykernel-instance)
|
||||||
|
- - [Setting up Identity Recovery contract](#setting-up-identity-recovery-contract)
|
||||||
|
- - [Recovering an Identity](#recovering-an-identity)
|
||||||
|
## Summary
|
||||||
|
This is a proposed proof of concept for the implementation of interfaces [ERC-725](https://github.com/ethereum/EIPs/issues/725) and [ERC735](https://github.com/ethereum/EIPs/issues/725), providing the following functionality:
|
||||||
|
- Public key register, composed of Ethereum Addresses and ECDSA keys. These keys can perform management activities over the identity itself, as well as performing operations in other contracts and transfer of ether.
|
||||||
|
- Claim holding, that can be used to add / verify claims against an identity.
|
||||||
|
- Identity factory to simplify the process of instantiating an Identity contract, as well as handling the upgrade process of this instance, assuming there's a new version of this contract.
|
||||||
|
- Identity recovery process that can be initiated using a shared secret among keys related to the identity.
|
||||||
|
|
||||||
|
## Smart Contracts Overview
|
||||||
|
- `Controlled`. Keeps tracks of the controller or owner of the contract. Provides the modifier `onlyController` which can be used in child contracts.
|
||||||
|
- `DelegatedCall`. Abstract contract that delegates calls using the `delegated` modifier to the result of `targetDelegatedCall()` function.
|
||||||
|
- `InstanceStorage`.
|
||||||
|
Defines kernel vars that an `IdentityKernel` contract share with a `Instance` contract. If you wish to reuse this contract, it is important to avoid overwriting wrong storage pointers, so `InstanceStorage` should be always the first contract to be inherited.
|
||||||
|
- `Instance`. Contract that forwards everything through `delegatecall` to a defined `IdentityKernel`. This contracts inherits from `InstanceStorage` and `DelegatedCall`
|
||||||
|
- `UpdatableInstance`. A contract that can be updated, if the contract itself calls `updateUpdatableInstance()`. This contract inherits from `Instance`
|
||||||
|
- `DelayedUpdatableInstanceStorage`. This contract defines kernel vars that an `IdentityKernel` contract shares with and `Instance`. See `InstanceStorage` fro restrictions in case of reuse. This contract inherits from `InstanceStorage`
|
||||||
|
- `DelayedUpdatableInstance`. Extending the functionality of `UpdatableInstance`, this contract introduces a delay functionality based in the `block.timestamp` in order to limit updates with a 30 days lock.
|
||||||
|
- `Factory`. Contract used as a version control for child factories contracts
|
||||||
|
- `ERC725` and `ERC735`. Interfaces based on EIPs [ERC-725: Identity](https://github.com/ethereum/EIPs/issues/725) and [ERC735: Claims Holder](https://github.com/ethereum/EIPs/issues/725)
|
||||||
|
- `Identity`. Implementation of ERC725 and ERC735. Includes additional management functions to handle minimum required approvals for execution of transactions, as well as recovery related functions.
|
||||||
|
- `IdentityKernel`. Represents a version of the identity contract that can be created with the `IdentityFactory`, as well as be updated with calls to itself. This contract inherits from `DelayedUpdatableInstanceStorage` and `Identity`
|
||||||
|
- `FriendsRecovery`. Contract that handles the recovery process of an `Identity` in case the management keys are lost, or were compromised. A `FriendsRecovery` contract instance has an 1:1 association with an `Identity`
|
||||||
|
- `IdentityFactory`. Deploys `DelayedUpdatableInstanceStorage` configured to a deployed `IdentityKernel` and initializes the Instance with `initIdentity` function from kernel.
|
||||||
|
|
||||||
|
## Tutorials
|
||||||
|
|
||||||
|
### Identity Creation
|
||||||
|
We recommend to not create `Identity` instances directly, but create them through the `IdentityFactory` which is deployed in: `0x000000000000000000000000`, and provides the following functions:
|
||||||
|
- `createIdentity()` - will create a new instance of an `IdentityKernel`, with `msg.sender` as a management key.
|
||||||
|
- `createIdentity(address _idOwner)` - used to specify the owner / management key of a new identity.
|
||||||
|
|
||||||
|
The event `IdentityCreated` is triggered when the new identity is created successfully.
|
||||||
|
|
||||||
|
### Executions and Approvals
|
||||||
|
Identities can perform management activities on themselves, as well as performing actions in other contracts. These operations are performed with the `execute()` and `approve()` functions.
|
||||||
|
|
||||||
|
`execute(address _to, uint256 _value, bytes _data)` is called when you wish to perform an operation. This function may be called by a management key or by an action key and triggers an `ExecutionRequested` event. Management keys are required when `_to` refers to the identity itself, otherwise, action keys are required.
|
||||||
|
|
||||||
|
The `_value` parameters refer to the amount in ether that this transaction will send to the contract/wallet address specified in `_to`. The identity contract should have funds if the value is greater than `0`.
|
||||||
|
|
||||||
|
`_data` refers to the byte encoding of the operations and parameters to be executed. `web3.eth.abi.encodeFunctionCall` is useful to generate the bytecode to be sent in this function. Here's an example on how to use it to call the `addKey` function of the identity contract:
|
||||||
|
|
||||||
|
```
|
||||||
|
const web3EthAbi = require("web3-eth-abi");
|
||||||
|
let data = web3EthAbi.encodeFunctionCall({
|
||||||
|
name: 'addKey',
|
||||||
|
type: 'function',
|
||||||
|
inputs: [{
|
||||||
|
type: 'bytes32',
|
||||||
|
name: '_key'
|
||||||
|
},{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_purpose'
|
||||||
|
},{
|
||||||
|
type: 'uint256',
|
||||||
|
name: '_type'
|
||||||
|
}]
|
||||||
|
}, ["0x1234567", 1, 1]);
|
||||||
|
|
||||||
|
let receipt = await identityContractInstance.execute(identityContractInstance.address, 0, data).send({from: accounts[0]});
|
||||||
|
```
|
||||||
|
A javascript utils library is provided in `utils/identityUtils.js` which can be used to generate the payloads used in the `_data` parameter of the `execute` function
|
||||||
|
|
||||||
|
Once the `execute` function is executed, if the minimum required approvals by key purpose is one, the transaction is executed immediatly (Triggering the `Approved` and `Executed` events).
|
||||||
|
|
||||||
|
In case the minimum required approvals are greater than 1, `approve(uint256 _id, bool _approval)` needs to be called by N management or action keys depending on the operation to perform, each call triggering an `Approved` event. Once the minimum approvals required is reached, the transaction will be executed immediatly. This `approve` function requires an transaction execution id, which can be obtained by the `ExecutionRequested` event triggered by the `execute` function.
|
||||||
|
|
||||||
|
### Management Activities
|
||||||
|
Identity management is limited to the addition, removal and setting of the minimum required approvals to perform a transaction. These activities will fall into the approval process requiring that N managers approve their execution before it is performed.
|
||||||
|
- `addKey(bytes32 _key, uint256 _purpose, uint256 _type)`. Registers a key in the identity. Keys should have a defined purpose and type. The `_purpose` of a key can be `1` - Management keys, used only to perform management activities; `2` - Action keys, used only to perform calls to external contracts and sending ether; `3` - Claim signer key, which are keys that can add claims to the identity; and `4` - Encryption keys, at the moment used only for information purposes. The `_type` of keys supported at the moment are `0` for ethereum addresses, `1` for ECDSA. Keys are stored as a bytes32 value. Both `_purpose` and `_type` accepts `uint256` types, so they're not limited to the values described in here. It is worth mentioning that keys can have more than one purpose and `addKey` can be called for the same key more than once, assuming the purpose is different each time it is called. Triggers a `KeyAdded` event.
|
||||||
|
- `removeKey(bytes32 _key, uint256 _purpose)`. Used to remove an existing key-purpose pair. It will fail if you're removing a management key, and there is only one management key registered in the identity. Triggers a `KeyRemoved` event.
|
||||||
|
- `replaceKey(bytes32 _oldKey, bytes32 _newKey, uint256 _newType)`. Used to replace an existing key, for a new one that can have a different type. Triggers both a `KeyRemoved` and `KeyAdded` event.
|
||||||
|
- `setMinimumApprovalsByKeyType(uint256 _purpose, uint256 _minimumApprovals)`. By default, an `Identity` has only one management key registered (the owner of the identity), however it is possible to have more than one management key registered with `addKey`, and require a N of M approvals (both for Management and Action keys). This is done with this function, where you have to specify number of minimum approvals required by key purpose.
|
||||||
|
|
||||||
|
### Claims
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
### Constants / View functions
|
||||||
|
The following functions are provided to access information about an identity:
|
||||||
|
- `getKey(bytes32 _key, uint256 _purpose)`. Returns the key type, purpose and key for a given key-purpose pair
|
||||||
|
- `isKeyPurpose(bytes32 _key, uint256 _purpose)` returns if a given key-purpose exists, and if it's purpose is actually what was specified.
|
||||||
|
- `getKeyPurpose(bytes32 _key)` returns an array of purposes for a given key.
|
||||||
|
- `function getKeysByPurpose(uint256 _purpose)` returns an array of keys for a given purpose.
|
||||||
|
- `getClaim(bytes32 _claimId)` returns the claim information registered for a given claim Id.
|
||||||
|
- `getClaimIdsByType(uint256 _claimType)` returns an array of claim Ids for a given claim type.
|
||||||
|
|
||||||
|
### Adding new functionality to identitities
|
||||||
|
New versions of identities should extend from `IdentityKernel` and need to be registered in the `IdentityFactory`. This is done by creating a new instance of the new contract which inherits from `IdentityKernel`, and then calling the `setKernel` function of the `IdentityFactory` specifiying the address of the updated identity kernel, and a `bytes32` info hash with the description of the new version.
|
||||||
|
Once updated, a `NewKernel` event is triggered.
|
||||||
|
|
||||||
|
### Upgrade an `IdentityKernel` instance
|
||||||
|
When an identity instance needs to be upgraded, we can use the execute/approve process to upgrade it to an specific version. This upgrade process requires using `execute` to call two functions
|
||||||
|
- `updateRequestUpdatableInstance(address _newKernel)`. This will generate a pending request for upgrading an instance `30 days` later and trigger an UpdateRequested event.
|
||||||
|
- `updateConfirmUpdatableInstance(address _newKernel)`. After `30 days` pass, this function needs to be invoked to confirm the update process. Once the update process is completed a UpdateConfirmed event is triggered.
|
||||||
|
- An request for update can be cancelled if it hasn't been approved yet. This is done using the function `updateCancelUpdatableInstance()` with the same execute/approve process
|
||||||
|
|
||||||
|
Kernel addresses could be obtained using the `getVersion` function of the `IdentityFactory`
|
||||||
|
|
||||||
|
### Setting up Identity Recovery contract
|
||||||
|
After creating an identity with the `IdentityFactory`, an instance of `FriendsRecovery` need to be created. The constructor of this contract expects the following parameters:
|
||||||
|
- `_identity`: The identity contract address
|
||||||
|
- `_setupDelay`: Time for users to be able to change the selected friends for recovery.
|
||||||
|
- `_threshold`: Minimum number of friends required to recover an identity
|
||||||
|
- `_secret`: sha3 of the identity address + a secret word
|
||||||
|
- `friendHashes`: an array of sha3 hashes compossed of the identity address + secret word + friend ethereum address.
|
||||||
|
|
||||||
|
Once this recovery contract is created, we need to associate it with the identity. This is done through the execute/approve mechanism of the identity, sending a payload to invoke the `setupRecovery` function of the identity, passing the recovery contract address as a parameter.
|
||||||
|
|
||||||
|
|
||||||
|
### Recovering an Identity
|
||||||
|
Recovery of an identity happens when you lose access to the management key(s) or Identity. The recovery is done having the friends sign a message. This message is a sha3 hash compossed of:
|
||||||
|
|
||||||
|
```
|
||||||
|
recovery address + secret word + contract to invoke (identity) + the function and parameters of the function to invoke encoded (recover function of identity) +
|
||||||
|
new secret word hash + new friend hashes.
|
||||||
|
```
|
||||||
|
|
||||||
|
Where new `new secret word hash` is a sha3 of the identity address + secret word; and `new friend hashes` is an array of sha3 hashes compossed of the identity address + secret word + friend ethereum address).
|
||||||
|
|
||||||
|
Normally the function that is going to be encoded should be the identity `managerReset` with the address of the new management key.
|
||||||
|
|
||||||
|
A minimum of (threshold) friends should approve this recovery attempt, and this can be done by them spending gas, calling the `approve` function of the recovery contract; or by having a single address (probably the identity owner) calling `approvePreSigned`.
|
||||||
|
|
||||||
|
`approve` could be called sending the sha3 hashed message described previously through a regular transaction , and `approvePreSigned` needs gathering the signatures of the hashed message into different arrays (for v, r, and s)
|
||||||
|
|
||||||
|
To enhance privacy, any account can approve anything, however only revealed addresses will be used.
|
||||||
|
|
||||||
|
Once the approvation is complete, the `execute` function of the recovery contract needs to be called, with the parameters used to generate the hashed message, and after the recovery is completed, `processManagerReset` needs to be executedn on the identity to remove all the management keys different from the new management key used for the recovery
|
||||||
|
|
||||||
|
An example of how to use the recovery contract is available in `test/friendsRecovery.js`.
|
||||||
|
|
136
test/factory.js
Normal file
136
test/factory.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const Embark = require('embark');
|
||||||
|
let EmbarkSpec = Embark.initTests();
|
||||||
|
let web3 = EmbarkSpec.web3;
|
||||||
|
|
||||||
|
const identityJson = require('../dist/contracts/Identity.json');
|
||||||
|
const updatedIdentityKernelJson = require('../dist/contracts/UpdatedIdentityKernel.json');
|
||||||
|
|
||||||
|
const TestUtils = require("../utils/testUtils.js")
|
||||||
|
const idUtils = require("../utils/identityUtils.js")
|
||||||
|
|
||||||
|
describe('IdentityFactory', function(accounts) {
|
||||||
|
|
||||||
|
let identityFactory;
|
||||||
|
let identity;
|
||||||
|
let updatedIdentity;
|
||||||
|
let updatedIdentityKernel;
|
||||||
|
|
||||||
|
before( function(done) {
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
EmbarkSpec = Embark.initTests();
|
||||||
|
web3 = EmbarkSpec.web3;
|
||||||
|
|
||||||
|
EmbarkSpec.deployAll({
|
||||||
|
"IdentityFactory": {
|
||||||
|
args: ["0xaaaa"],
|
||||||
|
gas: 5000000
|
||||||
|
},
|
||||||
|
"Identity": {},
|
||||||
|
"UpdatedIdentityKernel": {}
|
||||||
|
}, (_accounts) => {
|
||||||
|
accounts = _accounts;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Creates a new identity", async () => {
|
||||||
|
let tx = await IdentityFactory.methods.createIdentity().send({from: accounts[0]});
|
||||||
|
|
||||||
|
const logEntry = tx.events.IdentityCreated;
|
||||||
|
|
||||||
|
assert(logEntry !== undefined, "IdentityCreated was not triggered");
|
||||||
|
|
||||||
|
let identity = new web3.eth.Contract(identityJson.abi, logEntry.returnValues.instance, {from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Registers a updated identity contract", async() => {
|
||||||
|
const infoHash = "0xbbbb";
|
||||||
|
let receipt = await IdentityFactory.methods.setKernel(UpdatedIdentityKernel.address, infoHash).send({from: accounts[0]});
|
||||||
|
|
||||||
|
const newKernel = TestUtils.eventValues(receipt, "NewKernel");
|
||||||
|
assert(newKernel.infohash, infoHash, "Infohash is not correct");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Creates a new identity using latest version", async() => {
|
||||||
|
let tx = await IdentityFactory.methods.createIdentity().send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.notEqual(tx.events.IdentityCreated, undefined, "IdentityCreated wasn't triggered");
|
||||||
|
|
||||||
|
const contractAddress = tx.events.IdentityCreated.returnValues.instance;
|
||||||
|
|
||||||
|
|
||||||
|
let updatedIdentity = new web3.eth.Contract(updatedIdentityKernelJson.abi, contractAddress, {from: accounts[0]});
|
||||||
|
|
||||||
|
tx = await updatedIdentity.methods.test().send({from: accounts[0]});
|
||||||
|
assert.notEqual(tx.events.TestFunctionExecuted, undefined, "TestFunctionExecuted wasn't triggered");
|
||||||
|
|
||||||
|
// Test if it still executes identity functions as expected
|
||||||
|
let baseIdentity = new web3.eth.Contract(identityJson.abi, contractAddress, {from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await baseIdentity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
|
||||||
|
1,
|
||||||
|
baseIdentity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Updates an identity to the latest version", async() => {
|
||||||
|
let tx1 = await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.updateRequestUpdatableInstance(UpdatedIdentityKernel.address))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.notEqual(tx1.events.Executed, undefined, "Executed wasn't triggered");
|
||||||
|
|
||||||
|
// Updating EVM timestamp to test delay
|
||||||
|
const plus31days = 60 * 60 * 24 * 31;
|
||||||
|
|
||||||
|
/*
|
||||||
|
// @rramos - The following code is supposed to increase by 31 days the evm date,
|
||||||
|
// and mine one block. It is commented because it seems to not be working on web3 1.0.
|
||||||
|
// Also, sendAsync is supposed to be named send in this version, yet it shows an error
|
||||||
|
// that it does not support synchronous executions. (?)
|
||||||
|
// TODO: figure it out!
|
||||||
|
|
||||||
|
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [plus31days], id: 0}, function(){console.log(1);});
|
||||||
|
web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 0}, function(){console.log(2);})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Confirm update
|
||||||
|
let tx2 = await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.updateConfirmUpdatableInstance(UpdatedIdentityKernel.address))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.notEqual(tx2.events.Executed, undefined, "Executed wasn't triggered");
|
||||||
|
|
||||||
|
|
||||||
|
let updatedIdentity1 = new web3.eth.Contract(updatedIdentityKernelJson.abi, Identity.address, {from: accounts[0]});
|
||||||
|
|
||||||
|
// Calling
|
||||||
|
let tx3 = await updatedIdentity1.methods.test().send({from: accounts[0]});
|
||||||
|
assert.notEqual(tx3.events.TestFunctionExecuted, undefined, "TestFunctionExecuted wasn't triggered");
|
||||||
|
assert.equal(
|
||||||
|
tx3.events.TestFunctionExecuted.returnValues.minApprovalsByManagementKeys.toString(10),
|
||||||
|
1,
|
||||||
|
Identity.address + " wasn't updated to last version");
|
||||||
|
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
497
test/identity.js
Normal file
497
test/identity.js
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const Embark = require('embark');
|
||||||
|
let EmbarkSpec = Embark.initTests();
|
||||||
|
let web3 = EmbarkSpec.web3;
|
||||||
|
const TestUtils = require("../utils/testUtils.js");
|
||||||
|
const idUtils = require('../utils/identityUtils.js');
|
||||||
|
|
||||||
|
describe("Identity", function() {
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
let accounts;
|
||||||
|
|
||||||
|
beforeEach( function(done) {
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
EmbarkSpec = Embark.initTests();
|
||||||
|
web3 = EmbarkSpec.web3;
|
||||||
|
|
||||||
|
EmbarkSpec.deployAll({
|
||||||
|
"Identity": {},
|
||||||
|
"TestContract": {}
|
||||||
|
}, (_accounts) => {
|
||||||
|
accounts = _accounts;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Identity()", () => {
|
||||||
|
it("initialize with msg.sender as management key", async () => {
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
Identity.address + ".getKeyPurpose("+accounts[0]+") is not MANAGEMENT_KEY");
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("addKey(address _key, uint256 _type)", () => {
|
||||||
|
it("MANAGEMENT_KEY add a new address as ACTION_KEY", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS)
|
||||||
|
).send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.ACTION,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not ACTION_KEY");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add key by non manager", async () => {
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[2]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.NONE,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not correct");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add key type 1 by actor", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[2], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[2]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.NONE,
|
||||||
|
Identity.address+".getKeyType("+accounts[1]+") is not correct");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fire KeyAdded(address indexed key, uint256 indexed type)", async () => {
|
||||||
|
let receipt = await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
const keyAdded = TestUtils.eventValues(receipt, "KeyAdded");
|
||||||
|
assert(keyAdded.key, TestUtils.addressToBytes32(accounts[1]), "Key is not correct")
|
||||||
|
assert(keyAdded.keyType, idUtils.types.ADDRESS, "Type is not correct")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("removeKey(address _key, uint256 _type)", () => {
|
||||||
|
it("MANAGEMENT_KEY should remove a key", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.MANAGEMENT))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.NONE,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not 0")
|
||||||
|
});
|
||||||
|
|
||||||
|
it("other key should not remove a key", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.MANAGEMENT, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.MANAGEMENT))
|
||||||
|
.send({from: accounts[2]});
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not 0")
|
||||||
|
});
|
||||||
|
|
||||||
|
it("actor key should not remove key", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[2], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.ACTION))
|
||||||
|
.send({from: accounts[2]});
|
||||||
|
|
||||||
|
assert.fail('should have reverted before');
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.ACTION,
|
||||||
|
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 () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[0], idUtils.purposes.MANAGEMENT))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
Identity.address+".getKeyType("+accounts[0]+") is not 1")
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fire KeyRemoved(address indexed key, uint256 indexed type)", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
let receipt = await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.removeKey(accounts[1], idUtils.purposes.ACTION))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
const keyRemoved = TestUtils.eventValues(receipt, "KeyRemoved");
|
||||||
|
assert(keyRemoved.key, TestUtils.addressToBytes32(accounts[1]), "Key is not correct");
|
||||||
|
assert(keyRemoved.keyType, idUtils.types.ADDRESS, "Type is not correct");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("getKeyPurpose(address _key)", () => {
|
||||||
|
|
||||||
|
it("should start only with initializer as only key", async () => {
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[0])).call(),
|
||||||
|
idUtils.purposes.MANAGEMENT,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[0]+") is not correct")
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.NONE,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get type 2 after addKey type 2", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.ACTION,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get type 3 after addKey type 3", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.CLAIM_SIGNER, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
await Identity.methods.getKeyPurpose(TestUtils.addressToBytes32(accounts[1])).call(),
|
||||||
|
idUtils.purposes.CLAIM_SIGNER,
|
||||||
|
Identity.address+".getKeyPurpose("+accounts[1]+") is not correct")
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
describe("getKeysByType(uint256 _type)", () => {
|
||||||
|
|
||||||
|
it("at initialization", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("after addKey", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("after removeKey", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe("execute(address _to, uint256 _value, bytes _data)", () => {
|
||||||
|
let functionPayload;
|
||||||
|
|
||||||
|
it("Identity should receive ether", async() => {
|
||||||
|
|
||||||
|
const amountToSend = web3.utils.toWei('0.05', "ether");
|
||||||
|
|
||||||
|
let idBalance0 = await web3.eth.getBalance(Identity.address);
|
||||||
|
|
||||||
|
await web3.eth.sendTransaction({from:accounts[0], to:Identity.address, value: amountToSend})
|
||||||
|
|
||||||
|
let idBalance1 = await web3.eth.getBalance(Identity.address);
|
||||||
|
|
||||||
|
assert.equal(web3.utils.toBN(idBalance0).add(web3.utils.toBN(amountToSend)).toString(), web3.utils.toBN(idBalance1).toString(), Identity.address + " did not receive ether");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ACTOR_KEY execute arbitrary transaction", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
|
||||||
|
functionPayload = web3.eth.abi.encodeFunctionCall({
|
||||||
|
name: 'test',
|
||||||
|
type: 'function',
|
||||||
|
inputs: []
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let receipt = await Identity.methods.execute(
|
||||||
|
TestContract.address,
|
||||||
|
0,
|
||||||
|
functionPayload)
|
||||||
|
.send({from: accounts[1]});
|
||||||
|
|
||||||
|
// @rramos - Commented because of error:
|
||||||
|
// The current provider doesn't support subscriptions: Provider
|
||||||
|
/*assert.notEqual(
|
||||||
|
await TestUtils.listenForEvent(TestContract.events.TestFunctionExecuted),
|
||||||
|
undefined,
|
||||||
|
"Test function was not executed"); */
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("MANAGEMENT_KEY cannot execute arbitrary transaction", async () => {
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
TestContract.address,
|
||||||
|
0,
|
||||||
|
functionPayload)
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
} catch(error) {
|
||||||
|
TestUtils.assertJump(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Other keys NOT execute arbitrary transaction", async () => {
|
||||||
|
try {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
TestContract.address,
|
||||||
|
0,
|
||||||
|
functionPayload)
|
||||||
|
.send({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.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
// Adding funds to contract
|
||||||
|
await web3.eth.sendTransaction({from:accounts[0], to:Identity.address, value: web3.utils.toWei('0.05', "ether")})
|
||||||
|
|
||||||
|
const amountToSend = web3.utils.toWei('0.01', "ether");
|
||||||
|
|
||||||
|
let idBalance0 = await web3.eth.getBalance(Identity.address);
|
||||||
|
let a2Balance0 = await web3.eth.getBalance(accounts[2]);
|
||||||
|
|
||||||
|
await Identity.methods.execute(
|
||||||
|
accounts[2],
|
||||||
|
amountToSend,
|
||||||
|
'0x')
|
||||||
|
.send({from: accounts[1]});
|
||||||
|
|
||||||
|
let idBalance1 = await web3.eth.getBalance(Identity.address);
|
||||||
|
let a2Balance1 = await web3.eth.getBalance(accounts[2]);
|
||||||
|
|
||||||
|
assert(web3.utils.toBN(idBalance1).toString(), web3.utils.toBN(idBalance0).sub(web3.utils.toBN(amountToSend)).toString(), "Contract did not send ether");
|
||||||
|
assert(web3.utils.toBN(a2Balance1).toString(), web3.utils.toBN(a2Balance0).add(web3.utils.toBN(amountToSend)).toString(), accounts[2] + " did not receive ether");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fire ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data)", async () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
let receipt = await Identity.methods.execute(
|
||||||
|
TestContract.address,
|
||||||
|
0,
|
||||||
|
functionPayload)
|
||||||
|
.send({from: accounts[1]});
|
||||||
|
|
||||||
|
const executionRequested = TestUtils.eventValues(receipt, "ExecutionRequested");
|
||||||
|
assert(executionRequested.to, TestContract.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 () => {
|
||||||
|
await Identity.methods.execute(
|
||||||
|
Identity.address,
|
||||||
|
0,
|
||||||
|
idUtils.encode.addKey(accounts[1], idUtils.purposes.ACTION, idUtils.types.ADDRESS))
|
||||||
|
.send({from: accounts[0]});
|
||||||
|
|
||||||
|
let receipt = await Identity.methods.execute(
|
||||||
|
TestContract.address,
|
||||||
|
0,
|
||||||
|
functionPayload)
|
||||||
|
.send({from: accounts[1]});
|
||||||
|
|
||||||
|
const executed = TestUtils.eventValues(receipt, "Executed")
|
||||||
|
assert(executed.to, TestContract.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)", () => {
|
||||||
|
|
||||||
|
it("MANAGEMENT_KEY should approve a claim", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("MANAGEMENT_KEY should approve a transaction", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("2 out of 3 MANAGEMENT_KEY should approve a transaction and execute it", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fire Approved(uint256 indexed executionId, bool approved)", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("getClaim(bytes32 _claimId)", () => {
|
||||||
|
|
||||||
|
it("Returns a claim by ID.", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getClaimIdsByType(uint256 _claimType)", () => {
|
||||||
|
it("Returns an array of claim IDs by type.", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addClaim(uint256 _claimType, address issuer, uint256 signatureType, bytes _signature, bytes _data, string _uri)", () => {
|
||||||
|
it("Requests the ADDITION of a claim from an issuer", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requests the CHANGE of a claim from an issuer", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeClaim(bytes32 _claimId)", () => {
|
||||||
|
it("Requests the DELETION of a claim from an issuer", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Requests the DELETION of a claim from identity", async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
68
test/identityExtended.js
Normal file
68
test/identityExtended.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
COMMENTED TEMPORARLY WHILE PROJECT IS MIGRATED TO EMBARK - @rramos
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
*/
|
190
utils/identityUtils.js
Normal file
190
utils/identityUtils.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
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…
x
Reference in New Issue
Block a user