var Identity = artifacts.require('Identity') var Counter = artifacts.require('Counter') var KeyManager = artifacts.require('KeyManager') var IdentityRegistry = artifacts.require('IdentityRegistry') var ClaimRegistry780 = artifacts.require('ClaimRegistry780') var IdentityFactory = artifacts.require('IdentityFactory') var MetaWallet = artifacts.require('MetaWallet') var SimpleToken = artifacts.require('SimpleToken') var Web3 = require('web3') const claimKey = '0x0000000000000000000000000000000000000000000000000000000000000000' const claimValue = '0x0000000000000000000000000000000000000000000000000000000000000123' const OWNER_KEY = '0x0000000000000000000000000000000000000000000000000000000000000000' const operationCall = 0 const web3 = new Web3(Web3.givenProvider) const getEncodedCall = (web3, instance, method, params = []) => { const contract = new web3.eth.Contract(instance.abi) return contract.methods[method](...params).encodeABI() } const sign = async (params, account) => { const signatureData = web3.utils.soliditySha3(...params) return await web3.eth.sign(signatureData, account) } const assertVMExecption = async (fn) => { try { await fn() throw null; } catch (error) { assert.include(String(error), 'VM Exception') } } contract('Identity', function(accounts) { it('should allow the owner to call execute', async function() { // Deploy contracts const identity = await Identity.new(accounts[0]) const counter = await Counter.new() const identityRegistry = await IdentityRegistry.new() // Counter should be 0 initially assert.equal((await counter.get()).toString(), '0') // Call counter.increment from identity const encodedCall = getEncodedCall(web3, counter, 'increment') const result = await identity.execute(operationCall, counter.address, 0, encodedCall, { from: accounts[0] }) // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) it('should be able to integrate with identity manager', async function() { // Deploy contracts const identity = await Identity.new(accounts[0]) const counter = await Counter.new() // Counter should be 0 initially assert.equal((await counter.get()).toString(), '0') // Transfer identity ownership to the key manager const keyManager = await KeyManager.new(identity.address, accounts[1], { from: accounts[1] }) await identity.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) // Call counter.increment from identity, through identity manager const encodedCall = getEncodedCall(web3, counter, 'increment') const result = await keyManager.execute(operationCall, counter.address, 0, encodedCall, { from: accounts[1] }) // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) it('should own itself via ERC1056', async function() { // Deploy contracts const identity = await Identity.new(accounts[0]) const identityRegistry = await IdentityRegistry.new() // Check that identity owns itself via ERC1056 const identityOwner = await identityRegistry.identityOwner(identity.address) assert.equal(identityOwner, identity.address) }) it('should be able to make a claim via ERC780', async function() { // Deploy contracts const identity = await Identity.new(accounts[0]) const claimRegistry780 = await ClaimRegistry780.new() // Call setClaim using identity const subject = accounts[1] const encodedCall = getEncodedCall(web3, claimRegistry780, 'setClaim', [subject, claimKey, claimValue]) const result = await identity.execute(operationCall, claimRegistry780.address, 0, encodedCall) // Check that claim was recorded const claim = await claimRegistry780.getClaim(identity.address, subject, claimKey) assert.equal(claim, claimValue) }) describe('gas cost comparison', async function() { let identity, identityWithManager, counter, keyManager, metaWallet, simpleToken beforeEach(async function() { identity = await Identity.new(accounts[0]) identityWithManager = await Identity.new(accounts[0]) metaWallet = await MetaWallet.new() counter = await Counter.new() keyManager = await KeyManager.new(identityWithManager.address, accounts[1], { from: accounts[1] }) await keyManager.addKey(web3.utils.padLeft(metaWallet.address, 64), 2, { from: accounts[1] }) await identityWithManager.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) simpleToken = await SimpleToken.new() await simpleToken.transfer(accounts[1], 10) await simpleToken.approve(metaWallet.address, 10, { from: accounts[1] }) await metaWallet.deposit(simpleToken.address, keyManager.address, 10, { from: accounts[1] }) const balance = Number(await metaWallet.balanceOf(simpleToken.address, keyManager.address)) assert.equal(balance, 10) }) it('without identity or manager', async function() { // Call counter.increment await counter.increment() // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) it('with identity, without manager', async function() { // Call counter.increment from identity const encodedCall = getEncodedCall(web3, counter, 'increment') await identity.execute(operationCall, counter.address, 0, encodedCall) // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) it('with identity and manager', async function() { // Call counter.increment from identity, through identity manager const encodedCall = getEncodedCall(web3, counter, 'increment') await keyManager.execute(operationCall, counter.address, 0, encodedCall, { from: accounts[1] }) // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) it('with identity and manager and meta wallet', async function() { // Call counter.increment from identity, through identity manager, through meta wallet // yes, this is getting really meta const encodedCall = getEncodedCall(web3, counter, 'increment') const nonceKey = web3.utils.soliditySha3("execute", counter.address, 0, encodedCall, keyManager.address) const nonce = Number(await metaWallet.getNonce(nonceKey)) const expiry = Math.floor( Date.now() / 1000 ) + 100 const signature = await sign([metaWallet.address, "execute", counter.address, 0, encodedCall, expiry, keyManager.address, simpleToken.address, 1, nonce], accounts[1]) await metaWallet.execute(counter.address, 0, encodedCall, expiry, signature, keyManager.address, simpleToken.address, 1, { from: accounts[2] }) // Check that increment was called assert.equal((await counter.get()).toString(), '1') }) }) }) contract('KeyManager', function(accounts) { it('should be able to add and remove keys', async function() { const identity = await Identity.new(accounts[0]) const keyManager = await KeyManager.new(identity.address, accounts[0]) const actionPurpose = 2 const emptyPurpose = 0 // add key await keyManager.addKey(web3.utils.padLeft(accounts[1], 64), actionPurpose) // check that key was added let purpose = await keyManager.getKey(web3.utils.padLeft(accounts[1], 64)) assert.equal(purpose, actionPurpose) // add key, signed let nonceKey = web3.utils.soliditySha3("addKeySigned", accounts[2], actionPurpose) let nonce = Number(await keyManager.getNonce(nonceKey)) let expiry = Math.floor( Date.now() / 1000 ) + 100 let signature = await sign([keyManager.address, "addKeySigned", accounts[2], actionPurpose, nonce, expiry], accounts[0]) await keyManager.addKeySigned(accounts[2], actionPurpose, expiry, signature, { from: accounts[3] }) // check that key was added purpose = await keyManager.getKey(web3.utils.padLeft(accounts[2], 64)) assert.equal(purpose, actionPurpose) // remove key await keyManager.removeKey(web3.utils.padLeft(accounts[1], 64)) // check that key was removed purpose = await keyManager.getKey(web3.utils.padLeft(accounts[1], 64)) assert.equal(purpose, emptyPurpose) // remove key, signed nonceKey = web3.utils.soliditySha3("removeKeySigned", accounts[2]) nonce = Number(await keyManager.getNonce(nonceKey)) expiry = Math.floor( Date.now() / 1000 ) + 100 signature = await sign([keyManager.address, "removeKeySigned", accounts[2], nonce, expiry], accounts[0]) await keyManager.removeKeySigned(accounts[2], expiry, signature, { from: accounts[3] }) // check that key was removed purpose = await keyManager.getKey(web3.utils.padLeft(accounts[2], 64)) assert.equal(purpose, emptyPurpose) }) it('should allow execution for action purposes', async function() { const identity = await Identity.new(accounts[0]) const keyManager = await KeyManager.new(identity.address, accounts[0]) const counter = await Counter.new() const actionPurpose = 2 await identity.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) // add key await keyManager.addKey(web3.utils.padLeft(accounts[1], 64), actionPurpose) // execute counter const encodedCall = getEncodedCall(web3, counter, 'increment') await keyManager.execute(operationCall, counter.address, 0, encodedCall, { from: accounts[1] }) assert.equal((await counter.get()).toString(), '1') // execute counter, signed let nonceKey = web3.utils.soliditySha3(operationCall, "executeSigned", counter.address, 0, encodedCall) let nonce = Number(await keyManager.getNonce(nonceKey)) let signature = await sign([keyManager.address, "executeSigned", operationCall, counter.address, 0, encodedCall, nonce, 0], accounts[1]) await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, 0, signature, { from: accounts[2] }) assert.equal((await counter.get()).toString(), '2') // remove key await keyManager.removeKey(web3.utils.padLeft(accounts[1], 64)) // execute counter should fail await assertVMExecption(async () => { await keyManager.execute(operationCall, counter.address, 0, encodedCall, { from: accounts[1] }) }) // execute counter, signed should fail nonceKey = web3.utils.soliditySha3("executeSigned", operationCall, counter.address, 0, encodedCall) nonce = Number(await keyManager.getNonce(nonceKey)) signature = await sign([keyManager.address, "executeSigned", operationCall, counter.address, 0, encodedCall, nonce, 0], accounts[1]) assert.equal(nonce, 1) await assertVMExecption(async () => { await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, 0, signature, { from: accounts[2] }) }) }) it('should not allow replay attacks', async function() { const identity = await Identity.new(accounts[0]) const keyManager = await KeyManager.new(identity.address, accounts[0]) const counter = await Counter.new() const actionPurpose = 2 await identity.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) // add key await keyManager.addKey(web3.utils.padLeft(accounts[1], 64), actionPurpose) // execute counter, signed const encodedCall = getEncodedCall(web3, counter, 'increment') let nonce = 0 signature = await sign([keyManager.address, "executeSigned", operationCall, counter.address, 0, encodedCall, nonce, 0], accounts[1]) await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, 0, signature, { from: accounts[2] }) assert.equal((await counter.get()).toString(), '1') // replay attack should fail await assertVMExecption(async () => { await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, 0, signature, { from: accounts[3] }) }) }) it('should be able to be deployed with identity in one transaction', async function() { // Deploy contracts const counter = await Counter.new() const identityFactory = await IdentityFactory.new() const metaWallet = await MetaWallet.new() // Create identity and manager with factory const result = await identityFactory.createIdentityWithMetaWallet(metaWallet.address) assert.equal(result.logs.length, 1) const { identity, manager } = result.logs[0].args assert.ok(identity) assert.ok(manager) // Test new contracts const keyManager = KeyManager.at(manager) const encodedCall = getEncodedCall(web3, counter, 'increment') await keyManager.execute(operationCall, counter.address, 0, encodedCall) assert.equal((await counter.get()).toString(), '1') }) it('should enforce expiry', async function() { const identity = await Identity.new(accounts[0]) const keyManager = await KeyManager.new(identity.address, accounts[0]) const counter = await Counter.new() const actionPurpose = 2 await identity.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) // add key, signed with invalid expiry let nonceKey = web3.utils.soliditySha3("addKeySigned", accounts[1], actionPurpose) let nonce = Number(await keyManager.getNonce(nonceKey)) let expiry = Math.floor( Date.now() / 1000 ) - 100 let signature = await sign([keyManager.address, "addKeySigned", accounts[1], actionPurpose, nonce, expiry], accounts[0]) await assertVMExecption(async () => { await keyManager.addKeySigned(accounts[1], actionPurpose, expiry, signature, { from: accounts[3] }) }) // add key, signed with valid expiry expiry = Math.floor( Date.now() / 1000 ) + 100 signature = await sign([keyManager.address, "addKeySigned", accounts[1], actionPurpose, nonce, expiry], accounts[0]) await keyManager.addKeySigned(accounts[1], actionPurpose, expiry, signature, { from: accounts[3] }) // execute counter, signed with invalid expiry const encodedCall = getEncodedCall(web3, counter, 'increment') nonceKey = web3.utils.soliditySha3("executeSigned", operationCall, counter.address, 0, encodedCall) nonce = Number(await keyManager.getNonce(nonceKey)) expiry = Math.floor( Date.now() / 1000 ) - 100 signature = await sign([keyManager.address, "executeSigned", operationCall, counter.address, 0, encodedCall, nonce, expiry], accounts[1]) await assertVMExecption(async () => { await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, expiry, signature, { from: accounts[2] }) }) // execute counter, signed with valid expiry expiry = Math.floor( Date.now() / 1000 ) + 100 signature = await sign([keyManager.address, "executeSigned", operationCall, counter.address, 0, encodedCall, nonce, expiry], accounts[1]) await keyManager.executeSigned(operationCall, counter.address, 0, encodedCall, expiry, signature, { from: accounts[2] }) assert.equal((await counter.get()).toString(), '1') // remove key, signed with invalid expiry nonceKey = web3.utils.soliditySha3("removeKeySigned", accounts[1]) nonce = Number(await keyManager.getNonce(nonceKey)) expiry = Math.floor( Date.now() / 1000 ) - 100 signature = await sign([keyManager.address, "removeKeySigned", accounts[1], nonce, expiry], accounts[0]) await assertVMExecption(async () => { await keyManager.removeKeySigned(accounts[1], expiry, signature, { from: accounts[3] }) }) let purpose = await keyManager.getKey(web3.utils.padLeft(accounts[1], 64)) assert.equal(purpose, actionPurpose) // add key, signed with valid expiry expiry = Math.floor( Date.now() / 1000 ) + 100 signature = await sign([keyManager.address, "removeKeySigned", accounts[1], nonce, expiry], accounts[0]) await keyManager.removeKeySigned(accounts[1], expiry, signature, { from: accounts[3] }) purpose = await keyManager.getKey(web3.utils.padLeft(accounts[1], 64)) assert.equal(purpose, 0) }) }) contract('MetaWallet', function(accounts) { it('should be able to deposit and withdraw tokens', async function() { // set up const metaWallet = await MetaWallet.new() const simpleToken = await SimpleToken.new() await simpleToken.transfer(accounts[1], 10) await simpleToken.approve(metaWallet.address, 10, { from: accounts[1] }) // check initial balances let balanceInMetaWallet = Number(await metaWallet.balanceOf(simpleToken.address, accounts[1])) let balance = Number(await simpleToken.balanceOf(accounts[1])) let metaWalletTotal = Number(await simpleToken.balanceOf(metaWallet.address)) assert.equal(balanceInMetaWallet, 0) assert.equal(balance, 10) assert.equal(metaWalletTotal, 0) // deposit and check new balances await metaWallet.deposit(simpleToken.address, accounts[1], 4, { from: accounts[1] }) balanceInMetaWallet = Number(await metaWallet.balanceOf(simpleToken.address, accounts[1])) balance = Number(await simpleToken.balanceOf(accounts[1])) metaWalletTotal = Number(await simpleToken.balanceOf(metaWallet.address)) assert.equal(balanceInMetaWallet, 4) assert.equal(balance, 6) assert.equal(metaWalletTotal, 4) // balance should not change if deposit fails await assertVMExecption(async () => { await metaWallet.deposit(simpleToken.address, accounts[1], 100, { from: accounts[1] }) }) balanceInMetaWallet = Number(await metaWallet.balanceOf(simpleToken.address, accounts[1])) balance = Number(await simpleToken.balanceOf(accounts[1])) metaWalletTotal = Number(await simpleToken.balanceOf(metaWallet.address)) assert.equal(balanceInMetaWallet, 4) assert.equal(balance, 6) assert.equal(metaWalletTotal, 4) // withdraw and check new balances await metaWallet.withdraw(simpleToken.address, accounts[1], 3, { from: accounts[1] }) balanceInMetaWallet = Number(await metaWallet.balanceOf(simpleToken.address, accounts[1])) balance = Number(await simpleToken.balanceOf(accounts[1])) metaWalletTotal = Number(await simpleToken.balanceOf(metaWallet.address)) assert.equal(balanceInMetaWallet, 1) assert.equal(balance, 9) assert.equal(metaWalletTotal, 1) // balance should not change if withdraw fails await assertVMExecption(async () => { await metaWallet.withdraw(simpleToken.address, accounts[1], 123, { from: accounts[1] }) }) balanceInMetaWallet = Number(await metaWallet.balanceOf(simpleToken.address, accounts[1])) balance = Number(await simpleToken.balanceOf(accounts[1])) metaWalletTotal = Number(await simpleToken.balanceOf(metaWallet.address)) assert.equal(balanceInMetaWallet, 1) assert.equal(balance, 9) assert.equal(metaWalletTotal, 1) }) it('should be able to facilitate a sponsored execution', async function() { // set up const identityWithManager = await Identity.new(accounts[0]) const metaWallet = await MetaWallet.new() const counter = await Counter.new() const keyManager = await KeyManager.new(identityWithManager.address, accounts[1], { from: accounts[1] }) await keyManager.addKey(web3.utils.padLeft(metaWallet.address, 64), 2, { from: accounts[1] }) await identityWithManager.setData(OWNER_KEY, web3.utils.padLeft(keyManager.address, 64)) const simpleToken = await SimpleToken.new() await simpleToken.transfer(accounts[1], 10) await simpleToken.approve(metaWallet.address, 10, { from: accounts[1] }) await metaWallet.deposit(simpleToken.address, keyManager.address, 10, { from: accounts[1] }) // Facilitate a sponsored execution // The identity manager will sign a message giving permission for the wallet to transfer 3 tokens in exchange for executing the call. const encodedCall = getEncodedCall(web3, counter, 'increment') const nonceKey = web3.utils.soliditySha3("execute", counter.address, 0, encodedCall, keyManager.address) const nonce = Number(await metaWallet.getNonce(nonceKey)) const expiry = Math.floor( Date.now() / 1000 ) + 100 const tokensToTransfer = 3 const signature = await sign([metaWallet.address, "execute", counter.address, 0, encodedCall, expiry, keyManager.address, simpleToken.address, tokensToTransfer, nonce], accounts[1]) await metaWallet.execute(counter.address, 0, encodedCall, expiry, signature, keyManager.address, simpleToken.address, tokensToTransfer, { from: accounts[2] }) // Check that increment was called and tokens have been transferred assert.equal((await counter.get()).toString(), '1') const balance = Number(await simpleToken.balanceOf(accounts[2])) assert.equal(balance, tokensToTransfer) }) })