Commit be84c812 authored by Alex Puig's avatar Alex Puig
Browse files

Merge branch 'factory' into 'master'

Factory

See merge request caelum-tech/caelum-identity!21
parents a7568f1f 85a2d541
Pipeline #77538094 passed with stage
in 2 minutes and 45 seconds
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
pragma solidity ^0.5.4;
contract Counter {
int private count = 0;
function incrementCounter() public {
count += 1;
}
function decrementCounter() public {
count -= 1;
}
function getCount() public view returns (int) {
return count;
}
}
\ No newline at end of file
......@@ -3,10 +3,8 @@ pragma solidity ^0.5.4;
// from https://github.com/ERC725Alliance/erc725/blob/6764caab5a840ad8e727ed92ca9066ae49cc1568/contracts/contracts/ERC725.sol
interface ERC725 {
event DataChanged(bytes32 indexed key, bytes value);
event OwnerChanged(address indexed ownerAddress);
event ContractCreated(address indexed contractAddress);
function changeOwner(address _owner) external;
function getData(bytes32 _key) external view returns (bytes memory _value);
function setData(bytes32 _key, bytes calldata _value) external;
function execute(uint256 _operationType, address _to, uint256 _value, bytes calldata _data) external;
......
pragma solidity ^0.5.4;
import "./Identity.sol";
import "./Ownable.sol";
contract IdFactory {
contract IdFactory is Ownable{
address public owner;
uint32 public numIdentifiers = 0;
struct Identifier {
address did; // person delegated to
address did; // person delegated to
uint version; // index of the voted proposal
uint level; // Verification level
}
mapping(bytes32=>Identifier) identifiers;
constructor(address _owner) public {
owner = _owner;
}
// Whitellist of Certificate Authorities.
mapping(address=>uint) authorities;
function createIdentity(bytes32 handler, address _owner) public {
assert(identifiers[handler].version == 0);
address newId = address(new Identity(_owner));
identifiers[handler] = Identifier({
did: newId,
version: 1
version: 1,
level: 1
});
numIdentifiers++;
// emit new Identity
}
function addAuthority(bytes32 handler, uint maxLevel) public onlyOwner {
address did = identifiers[handler].did;
authorities[did] = maxLevel;
}
function removeAuthority(bytes32 handler) public onlyOwner {
address did = identifiers[handler].did;
authorities[did] = 0;
}
function setLevel(bytes32 handler, uint level) public {
require(authorities[msg.sender] >= level, "CA: caller is not an authorized CA");
identifiers[handler].level = level;
}
/**
* Returns the address associated with an ENS node.
* @param handler The NS handler to query.
......@@ -34,4 +50,13 @@ contract IdFactory {
function resolve(bytes32 handler) public view returns (address) {
return identifiers[handler].did;
}
/**
* Returns the address associated with an ENS node.
* @param handler The NS handler to query.
* @return The associated level.
*/
function level(bytes32 handler) public view returns (uint) {
return identifiers[handler].level;
}
}
\ No newline at end of file
pragma solidity ^0.5.4;
import "./ERC725.sol";
import "./Token.sol";
import "./Ownable.sol";
// From https://github.com/ERC725Alliance/erc725/blob/551bc004106b810b4bf2041c7986d457732818ec/contracts/contracts/Identity.sol
contract Identity is ERC725 {
// Identity Contract.
contract Identity is ERC725, Ownable {
uint256 constant OPERATION_CALL = 0;
uint256 constant OPERATION_CREATE = 1;
// bytes32 constant KEY_OWNER = 0x0000000000000000000000000000000000000000000000000000000000000000;
// Events.
event MetaTxDone(address indexed from, address indexed to, address indexed signed);
// Key/Value Store.
mapping(bytes32 => bytes) store;
address public owner;
// Nonce for Meta transactions.
uint public nonce;
// Set the ownership to the new Owner.
constructor(address _owner) public {
owner = _owner;
nonce = 0;
transferOwnership(_owner);
}
modifier onlyOwner() {
require(msg.sender == owner, "only-owner-allowed");
_;
}
// function toAddress(bytes32 a) internal pure returns (address b){
// assembly {
// mstore(0, a)
// b := mload(0)
// }
// return b;
// }
// function toBytes32(address a) internal pure returns (bytes32 b){
// assembly {
// mstore(0, a)
// b := mload(0)
// }
// return b;
// }
// ----------------
// Public functions
function () external payable {}
function changeOwner(address _owner)
external
onlyOwner
{
owner = _owner;
emit OwnerChanged(owner);
}
function getData(bytes32 _key)
external
view
......@@ -69,13 +43,8 @@ contract Identity is ERC725 {
emit DataChanged(_key, _value);
}
function transfer(address _token, address _to, uint256 _value) external onlyOwner {
Token(_token).transfer(_to, _value);
}
function execute(uint256 _operationType, address _to, uint256 _value, bytes calldata _data)
external
onlyOwner
function execute(uint256 _operationType, address _to, uint256 _value, bytes memory _data)
public onlyOwner
{
if (_operationType == OPERATION_CALL) {
executeCall(_to, _value, _data);
......@@ -88,6 +57,35 @@ contract Identity is ERC725 {
}
}
function getHash(address _to, uint256 _value, bytes memory _data)
public view returns(bytes32)
{
return keccak256(abi.encodePacked(this.owner(), address(this), _to, _value, _data, nonce));
}
function metatx(
address _to,
uint256 _value,
bytes calldata _data,
uint8 v,
bytes32 r,
bytes32 s)
external
{
// Check the tx is signed by the owner of the contract.
bytes32 _hash = getHash(_to, _value, _data);
// Increment the hash so this tx can't run again
nonce++;
address signed_by = ecrecover(_hash, v, r, s);
// if (checkSigner(_hash, signature)) {
if (signed_by == this.owner()) {
executeCall(_to, _value, _data);
emit MetaTxDone(msg.sender, _to, signed_by);
}
}
// copied from GnosisSafe
// https://github.com/gnosis/safe-contracts/blob/v0.0.2-alpha/contracts/base/Executor.sol
function executeCall(address to, uint256 value, bytes memory data)
......
pragma solidity ^0.5.4;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
\ No newline at end of file
pragma solidity ^0.5.4;
import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";
/**
* @title ERC-20 Mintable Token
* @dev Implements a currency in the Caelum network
*/
contract Token is ERC20Mintable {
string public symbol = "";
uint8 public decimals = 0;
/**
* @dev Constructor
* @param _to The address that will receive the minted tokens.
* @param _supply The number of tokens to mint.
* @param _symbol The token symbol.
* @param _decimals The number of decimal points
*/
constructor(address _to, uint _supply, string memory _symbol, uint8 _decimals) public {
symbol = _symbol;
decimals = _decimals;
mint(_to, _supply);
}
}
{
"name": "@caelum-tech/identity",
"version": "1.2.2",
"version": "1.3.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -3529,6 +3529,12 @@
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
......@@ -5990,6 +5996,16 @@
}
}
},
"truffle-assertions": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/truffle-assertions/-/truffle-assertions-0.9.1.tgz",
"integrity": "sha512-MtcyXMTzRfg8WfE3TfrbVJm9HWMTPFksWg0K/8ZhajaxzFyPJ56/9AzNjQCROQluI0X1vs6XDsegwMlT1UFUNw==",
"dev": true,
"requires": {
"assertion-error": "^1.1.0",
"lodash.isequal": "^4.5.0"
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
......
{
"name": "@caelum-tech/identity",
"version": "1.3.0",
"version": "1.3.1",
"description": "Caelum Identity library",
"main": "index.js",
"scripts": {
......@@ -41,11 +41,12 @@
"eslint-plugin-standard": "^4.0.0",
"ethers": "^4.0.27",
"fs-extra": "^8.0.1",
"openzeppelin-solidity": "^2.3.0",
"mocha": "^6.1.4",
"openzeppelin-solidity": "^2.3.0",
"solidity-coverage": "github:sc-forks/solidity-coverage#leapdao",
"solium": "^1.2.4",
"truffle": "^5.0.25"
"truffle": "^5.0.25",
"truffle-assertions": "^0.9.1"
},
"directories": {
"test": "test"
......
const ethers = require('ethers')
const expect = require('chai').expect
const expectThrow = require('./helpers/expectThrow.js')
const truffleAssert = require('truffle-assertions')
const Identity = artifacts.require('./contracts/Identity.sol')
const Counter = artifacts.require('./contracts/Counter.sol')
// If ethers.js wasn't so rudimentary we could access these values
// through the contract interface, but it is and we can't.
const OPERATION_CALL = 0
const OPERATION_CREATE = 1
const OPERATION_INVALID = 333 // invented
const key = ethers.utils.formatBytes32String('test')
const valueStr = 'Hello World'
const value = ethers.utils.formatBytes32String(valueStr)
const idInterface = new ethers.utils.Interface(Identity.abi)
const counterInterface = new ethers.utils.Interface(Counter.abi)
contract('Identity', (accounts) => {
const owner1 = accounts[1]
const owner2 = accounts[1]
const rando = accounts[3]
let identity1, identity2
it('Should Deploy the Identity Contract', async () => {
identity1 = await Identity.new(owner1)
expect(await identity1.owner()).to.be.equal(owner1)
})
it('Should test the Identity contract payable function', async () => {
web3.eth.sendTransaction({ from: rando, to: identity1.address, value: web3.utils.toWei('1', 'ether') })
})
it('Should create Identity 2 from Identity 1', async () => {
const encodedCall = idInterface.deployFunction.encode(Identity.bytecode, [owner1])
const tx = await identity1.execute(OPERATION_CREATE, identity1.address, 0, encodedCall, { from: owner1 })
truffleAssert.eventEmitted(tx, 'OwnershipTransferred', (ev) => {
return ev.previousOwner === '0x0000000000000000000000000000000000000000' && ev.newOwner === identity1.address
})
truffleAssert.eventEmitted(tx, 'OwnershipTransferred', (ev) => {
return ev.previousOwner === identity1.address && ev.newOwner === owner1
})
truffleAssert.eventEmitted(tx, 'ContractCreated', (ev) => {
return ev.contractAddress !== null
})
const idAddress = tx.logs[2].args[0]
identity2 = await Identity.at(idAddress)
expect(await identity2.owner()).to.eq(owner1)
})
it('Should change back the owner to Identity 1', async () => {
await identity2.transferOwnership(identity1.address, { from: owner1 })
expect(await identity1.owner()).to.eq(owner1)
})
it('Should work as expected : setData and getData', (done) => {
identity1.setData(key, value, { from: owner2 })
.then(async (tx) => {
expect(tx.logs[0].event).to.eq('DataChanged')
return identity1.getData(key)
})
.then((resValue) => {
expect(ethers.utils.parseBytes32String(resValue)).to.be.equal(valueStr)
done()
})
})
it('Should act as a proxy and change Owner again', async () => {
const encodedCall = idInterface.functions.transferOwnership.encode([owner2])
const tx = await identity1.execute(OPERATION_CALL, identity2.address, 0, encodedCall, { from: owner1 })
truffleAssert.eventEmitted(tx, 'OwnershipTransferred', (ev) => {
return ev.previousOwner === identity1.address && ev.newOwner === owner2
})
expect(await identity2.owner()).to.eq(owner2)
})
it('Should not waste gas on an invalid transaction type', async () => {
await expectThrow(identity1.execute(OPERATION_INVALID, identity1.address, 0, value, { from: owner2 }))
})
it('Should be able to send MetaTX', async () => {
// Create a new wallet and set it as owner for identity2
let counter = await Counter.new(owner1)
let wallet = ethers.Wallet.createRandom()
let tx = await identity2.transferOwnership(wallet.address, { from: owner2 })
expect(await identity2.owner()).to.eq(wallet.address)
truffleAssert.eventEmitted(tx, 'OwnershipTransferred', (ev) => {
return ev.contractAddress !== null
})
// Prepare the metatx
const encodedCall = counterInterface.functions.incrementCounter.encode([])
const hash = await identity2.getHash(counter.address, 0, encodedCall)
// Sign the message.
let flatSig = wallet.signingKey.signDigest(hash)
let signature = ethers.utils.splitSignature(flatSig)
expect(ethers.utils.recoverAddress(hash, signature)).to.eq(wallet.address)
// Try the metatx
expect(Number(await counter.getCount())).to.eq(0)
tx = await identity2.metatx(counter.address, 0, encodedCall, signature.v, signature.r, signature.s)
truffleAssert.eventEmitted(tx, 'MetaTxDone', (ev) => {
return (ev.from === accounts[0] && ev.to === counter.address && ev.signed == wallet.address)
})
expect(Number(await counter.getCount())).to.eq(1)
})
})
const ethers = require('ethers')
const expect = require('chai').expect
const expectThrow = require('./helpers/expectThrow.js')
const Identity = artifacts.require('./contracts/Identity.sol')
const IdFactory = artifacts.require('./contracts/IdFactory.sol')
const OPERATION_CALL = 0
contract('IdFactory', (accounts) => {
let factory
const owner = accounts[0]
// Certificate Authority 1
const ca1 = accounts[1]
const ca1h = ethers.utils.formatBytes32String('ca1')
// Certificate Authority 2
const ca2 = accounts[2]
const ca2h = ethers.utils.formatBytes32String('ca2')
// Identity Owner 1
const id1 = accounts[3]
const id1h = ethers.utils.formatBytes32String('test1')
it('Should Deploy the Factory Contract', async () => {
factory = await IdFactory.new()
expect(await factory.owner()).to.be.equal(owner)
})
it('Should Add a new Identity', async () => {
// Add identity
await factory.createIdentity(ca1h, ca1)
expect(Number(await factory.numIdentifiers())).to.be.equal(1)
// Check Identity contract.
const newId = await factory.resolve(ca1h)
const identifier = await Identity.at(newId)
expect(await identifier.owner()).to.be.equal(ca1)
})
it('Adding another Identity should increase the number of identities', async () => {
await factory.createIdentity(ca2h, ca2)
expect(Number(await factory.numIdentifiers())).to.be.equal(2)
})
it('Should Not duplicate handlers', async () => {
await expectThrow(factory.createIdentity(ca2h, ca2))
})
it('Should add authorities', async () => {
// add autority 1. Max level 3.
await factory.addAuthority(ca1h, 3)
// Only owner can add authorities.
await expectThrow(factory.addAuthority(ca2h, 9, { from: ca2 }))
// add autority 2. Max level 9.
await factory.addAuthority(ca2h, 9)
})
it('Should set the level for a user', async () => {
// Set Level to a user.
await factory.createIdentity(id1h, id1)
const authority1 = await Identity.at(await factory.resolve(ca1h))
const iface = new ethers.utils.Interface(IdFactory.abi)
const call1 = iface.functions.setLevel.encode([id1h, '3'])
await authority1.execute(OPERATION_CALL, factory.address, 0, call1, { from: ca1 })
expect(Number(await factory.level(id1h))).to.be.equal(3)
// Try to set a level higher than the actual permissions. And fail.
const call2 = iface.functions.setLevel.encode([id1h, '6'])
authority1.execute(OPERATION_CALL, factory.address, 0, call2, { from: ca1 })
expect(Number(await factory.level(id1h))).to.be.equal(3)
// await factory.setLevel(id1h, 3, { from: ca1 })
// await expectThrow(factory.setLevel(id1h, 6, { from: ca1 }))
})
})
const ethers = require('ethers')
const expect = require('chai').expect
const expectThrow = require('./helpers/expectThrow.js')
const Identity = artifacts.require('./contracts/Identity.sol')
const Token = artifacts.require('./contracts/Token.sol')
// If ethers.js wasn't so rudimentary we could access these values