diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..064cf34 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "extends": [ + "airbnb-base" + ], + "rules": { + "no-console": "off", + "no-underscore-dangle": "off", + "import/no-extraneous-dependencies": "off", + "strict": "off" + }, + "env": { + "mocha": true, + "node": true, + "es6": true + }, + "globals": { + "artifacts": false, + "contract": false, + "assert": false + } +} \ No newline at end of file diff --git a/contracts/abi/CreateAndAddModules.abi b/contracts/abi/CreateAndAddModules.abi new file mode 100644 index 0000000..a18df2f --- /dev/null +++ b/contracts/abi/CreateAndAddModules.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"module","type":"address"}],"name":"enableModule","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"proxyFactory","type":"address"},{"name":"data","type":"bytes"}],"name":"createAndAddModules","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/abi/DelayedRecoveryModule.abi b/contracts/abi/DelayedRecoveryModule.abi new file mode 100644 index 0000000..a647463 --- /dev/null +++ b/contracts/abi/DelayedRecoveryModule.abi @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"INITIATE_RECOVERY_STRUCT_TYPEHASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR_TYPEHASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"RESET_RECOVERY_OWNER_STRUCT_TYPEHASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"activeRecoveryInfo","outputs":[{"name":"prevOwner","type":"address"},{"name":"oldOwner","type":"address"},{"name":"newOwner","type":"address"},{"name":"executionBlockHeight","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"manager","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ABORT_RECOVERY_STRUCT_TYPEHASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"recoveryOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"NAME","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"recoveryBlockDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"recoveryController","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"domainSeparator","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VERSION","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_prevOwner","type":"address"},{"indexed":false,"name":"_oldOwner","type":"address"},{"indexed":false,"name":"_newOwner","type":"address"}],"name":"RecoveryInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_prevOwner","type":"address"},{"indexed":false,"name":"_oldOwner","type":"address"},{"indexed":false,"name":"_newOwner","type":"address"}],"name":"RecoveryExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_prevOwner","type":"address"},{"indexed":false,"name":"_oldOwner","type":"address"},{"indexed":false,"name":"_newOwner","type":"address"}],"name":"RecoveryAborted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_oldRecoveryOwner","type":"address"},{"indexed":false,"name":"_newRecoveryOwner","type":"address"}],"name":"ResetRecoveryOwner","type":"event"},{"constant":false,"inputs":[{"name":"_recoveryOwner","type":"address"},{"name":"_recoveryController","type":"address"},{"name":"_recoveryBlockDelay","type":"uint256"}],"name":"setup","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_prevOwner","type":"address"},{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"},{"name":"_v","type":"uint8"}],"name":"initiateRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_prevOwner","type":"address"},{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"}],"name":"executeRecovery","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_prevOwner","type":"address"},{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"},{"name":"_v","type":"uint8"}],"name":"abortRecoveryByOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_prevOwner","type":"address"},{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"}],"name":"abortRecoveryByController","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newRecoveryOwner","type":"address"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"},{"name":"_v","type":"uint8"}],"name":"resetRecoveryOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/bin/CreateAndAddModules.bin b/contracts/bin/CreateAndAddModules.bin new file mode 100644 index 0000000..dae83fb --- /dev/null +++ b/contracts/bin/CreateAndAddModules.bin @@ -0,0 +1 @@ +0x608060405234801561001057600080fd5b506102e2806100206000396000f3fe60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360df7f5814610051578063610b592514610139575b600080fd5b34801561005d57600080fd5b506101376004803603604081101561007457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156100b157600080fd5b8201836020820111156100c357600080fd5b803590602001918460018302840111640100000000831117156100e557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061018a565b005b34801561014557600080fd5b506101886004803603602081101561015c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506102b1565b005b600081519050600080600090505b828110156102aa5780840160200151818501604001604051600060208285858c5af414156101c557600080fd5b73ffffffffffffffffffffffffffffffffffffffff8151169450602080601f85010402602001840193505050503073ffffffffffffffffffffffffffffffffffffffff1663610b5925836040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561028d57600080fd5b505af11580156102a1573d6000803e3d6000fd5b50505050610198565b5050505050565b600080fdfea165627a7a72305820c61d1a6a741c9e94077b28e3649b12d2e663efba81622bde4a4b1bd9f6a825530029 \ No newline at end of file diff --git a/contracts/bin/DelayedRecoveryModule.bin b/contracts/bin/DelayedRecoveryModule.bin new file mode 100644 index 0000000..7e4894e --- /dev/null +++ b/contracts/bin/DelayedRecoveryModule.bin @@ -0,0 +1 @@ +0x608060405234801561001057600080fd5b50611954806100206000396000f3fe6080604052600436106100fb5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166307218f4381146101005780631db61b54146101275780631de52d4e1461013c578063363893ae146101515780633811b1091461019b578063481c6a75146101e557806365ae42ae14610216578063698eb6681461025b5780637525c346146102a05780637ded0af7146102f95780638583c7c01461030e5780638931417414610323578063a3f4df7e1461037c578063bde4459014610406578063cbe406221461041b578063f48da4751461045e578063f698da2514610473578063ffa1ad7414610488575b600080fd5b34801561010c57600080fd5b5061011561049d565b60408051918252519081900360200190f35b34801561013357600080fd5b5061011561051d565b34801561014857600080fd5b50610115610578565b34801561015d57600080fd5b506101666105f8565b60408051600160a060020a03958616815293851660208501529190931682820152606082019290925290519081900360800190f35b3480156101a757600080fd5b506101e3600480360360808110156101be57600080fd5b508035600160a060020a0316906020810135906040810135906060013560ff1661061b565b005b3480156101f157600080fd5b506101fa61079f565b60408051600160a060020a039092168252519081900360200190f35b34801561022257600080fd5b506101e36004803603606081101561023957600080fd5b50600160a060020a0381358116916020810135821691604090910135166107ae565b34801561026757600080fd5b506101e36004803603606081101561027e57600080fd5b50600160a060020a0381358116916020810135821691604090910135166109ba565b3480156102ac57600080fd5b506101e3600480360360c08110156102c357600080fd5b508035600160a060020a039081169160208101358216916040820135169060608101359060808101359060a0013560ff16610e2f565b34801561030557600080fd5b50610115611075565b34801561031a57600080fd5b506101fa6110f5565b34801561032f57600080fd5b506101e3600480360360c081101561034657600080fd5b508035600160a060020a039081169160208101358216916040820135169060608101359060808101359060a0013560ff16611104565b34801561038857600080fd5b50610391611312565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103cb5781810151838201526020016103b3565b50505050905090810190601f1680156103f85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041257600080fd5b50610115611349565b34801561042757600080fd5b506101e36004803603606081101561043e57600080fd5b50600160a060020a0381358116916020810135909116906040013561134f565b34801561046a57600080fd5b506101fa6115fc565b34801561047f57600080fd5b5061011561160b565b34801561049457600080fd5b50610391611611565b604080517f496e6974696174655265636f766572795374727563742861646472657373207081527f7265764f776e65722c61646472657373206f6c644f776e65722c61646472657360208201527f73206e65774f776e65722900000000000000000000000000000000000000000081830152905190819003604b01902081565b604080517f454950373132446f6d61696e28616464726573732064656c617965645265636f81527f766572794d6f64756c65290000000000000000000000000000000000000000006020820152905190819003602b01902081565b604080517f52657365745265636f766572794f776e6572537472756374286164647265737381527f206f6c645265636f766572794f776e65722c61646472657373206e657752656360208201527f6f766572794f776e65722900000000000000000000000000000000000000000081830152905190819003604b01902081565b600654600754600854600954600160a060020a0393841693928316929091169084565b600354600160a060020a0316331461067f576040805160e560020a62461bcd02815260206004820152602c60248201526000805160206118e98339815191526044820152600080516020611909833981519152606482015290519081900360840190fd5b600160a060020a0384161515610705576040805160e560020a62461bcd02815260206004820152602560248201527f4e6577207265636f76657279206f776e6572277320616464726573732069732060448201527f6e756c6c2e000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b60045460009061071e90600160a060020a031686611648565b905061072c81858585611712565b60048054600160a060020a0387811673ffffffffffffffffffffffffffffffffffffffff19831617928390556040805192821680845293909116602083015280517f694d34d730078c03a64a5c5937027a85a71f3631085fb14efebcefba0f1200ad9281900390910190a1505050505050565b600154600160a060020a031681565b600354600160a060020a03163314610812576040805160e560020a62461bcd02815260206004820152602c60248201526000805160206118e98339815191526044820152600080516020611909833981519152606482015290519081900360840190fd5b60095460001061086c576040805160e560020a62461bcd02815260206004820152601c60248201527f5468657265206973206e6f20616374697665207265636f766572792e00000000604482015290519081900360640190fd5b600654839083908390600160a060020a03808516911614801561089c5750600754600160a060020a038381169116145b80156108b55750600854600160a060020a038281169116145b1515610931576040805160e560020a62461bcd02815260206004820152602481018290527f54686520657865637574696f6e20726571756573742773206461746120646f6560448201527f73206e6f74206d6174636820776974682074686520616374697665206f6e652e606482015290519081900360840190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff199081169091556007805482169055600880549091169055600060095560408051600160a060020a038881168252878116602083015286168183015290517f7789739c172389f440865a4d26a746e433bc23a4058077237b7c8cf47a9a18bb9181900360600190a1505050505050565b600354600160a060020a03163314610a1e576040805160e560020a62461bcd02815260206004820152602c60248201526000805160206118e98339815191526044820152600080516020611909833981519152606482015290519081900360840190fd5b600954600010610a78576040805160e560020a62461bcd02815260206004820152601c60248201527f5468657265206973206e6f20616374697665207265636f766572792e00000000604482015290519081900360640190fd5b600654839083908390600160a060020a038085169116148015610aa85750600754600160a060020a038381169116145b8015610ac15750600854600160a060020a038281169116145b1515610b3d576040805160e560020a62461bcd02815260206004820152602481018290527f54686520657865637574696f6e20726571756573742773206461746120646f6560448201527f73206e6f74206d6174636820776974682074686520616374697665206f6e652e606482015290519081900360840190fd5b6009544311610bbc576040805160e560020a62461bcd02815260206004820152603860248201527f5265717569726564206e756d626572206f6620626c6f636b7320746f2072656360448201527f6f76657220776173206e6f742070726f677265737365642e0000000000000000606482015290519081900360840190fd5b60408051600160a060020a03808916602480840191909152818916604480850191909152828916606480860191909152855180860382018152608490950186526020850180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fe318b52b0000000000000000000000000000000000000000000000000000000017905260015495517f468721a7000000000000000000000000000000000000000000000000000000008152959093166004860181815260009387018490529495909463468721a794869493889385939291810191018360ff168152602001828103825284818151815260200191508051906020019080838360005b83811015610cd4578181015183820152602001610cbc565b50505050905090810190601f168015610d015780820380516001836020036101000a031916815260200191505b5095505050505050602060405180830381600087803b158015610d2357600080fd5b505af1158015610d37573d6000803e3d6000fd5b505050506040513d6020811015610d4d57600080fd5b50511515610da5576040805160e560020a62461bcd02815260206004820152601a60248201527f5265636f7665727920657865637574696f6e206661696c65642e000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff199081169091556007805482169055600880549091169055600060095560408051600160a060020a038981168252888116602083015287168183015290517f39a93e422776c9b292c4617cb75d1f0bb8e528c2c06ea7cbc10cd233793a4f859181900360600190a150505050505050565b600954600010610e89576040805160e560020a62461bcd02815260206004820152601c60248201527f5468657265206973206e6f20616374697665207265636f766572792e00000000604482015290519081900360640190fd5b600654869086908690600160a060020a038085169116148015610eb95750600754600160a060020a038381169116145b8015610ed25750600854600160a060020a038281169116145b1515610f4e576040805160e560020a62461bcd02815260206004820152602481018290527f54686520657865637574696f6e20726571756573742773206461746120646f6560448201527f73206e6f74206d6174636820776974682074686520616374697665206f6e652e606482015290519081900360840190fd5b604080517f41626f72745265636f766572795374727563742861646472657373207072657681527f4f776e65722c61646472657373206f6c644f776e65722c61646472657373206e60208201527f65774f776e657229000000000000000000000000000000000000000000000000818301529051908190036048019020600090610fda908b8b8b61180e565b9050610fe881888888611712565b6006805473ffffffffffffffffffffffffffffffffffffffff199081169091556007805482169055600880549091169055600060095560408051600160a060020a038c811682528b811660208301528a168183015290517f7789739c172389f440865a4d26a746e433bc23a4058077237b7c8cf47a9a18bb9181900360600190a150505050505050505050565b604080517f41626f72745265636f766572795374727563742861646472657373207072657681527f4f776e65722c61646472657373206f6c644f776e65722c61646472657373206e60208201527f65774f776e65722900000000000000000000000000000000000000000000000081830152905190819003604801902081565b600454600160a060020a031681565b600354600160a060020a03163314611168576040805160e560020a62461bcd02815260206004820152602c60248201526000805160206118e98339815191526044820152600080516020611909833981519152606482015290519081900360840190fd5b600954156111c0576040805160e560020a62461bcd02815260206004820152601c60248201527f546865726520697320616e20616374697665207265636f766572792e00000000604482015290519081900360640190fd5b604080517f496e6974696174655265636f766572795374727563742861646472657373207081527f7265764f776e65722c61646472657373206f6c644f776e65722c61646472657360208201527f73206e65774f776e65722900000000000000000000000000000000000000000081830152905190819003604b01902060009061124c9088888861180e565b905061125a81858585611712565b60408051608081018252600160a060020a03808a168083528982166020808501829052928a16848601819052600554430160609586018190526006805473ffffffffffffffffffffffffffffffffffffffff1990811686179091556007805482168517905560088054909116831790556009558551928352928201528084019190915291517f984ae6bb7fc6ec202143bd903c70af8408ed9b154ef3e4cfe78e47a90ebbe2e49281900390910190a150505050505050565b60408051808201909152601781527f44656c61796564205265636f76657279204d6f64756c65000000000000000000602082015281565b60055481565b600254156113cd576040805160e560020a62461bcd02815260206004820152602160248201527f446f6d61696e20736570617261746f722077617320616c72656164792073657460448201527f2e00000000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b600160a060020a0383161515611453576040805160e560020a62461bcd02815260206004820152602160248201527f5265636f76657279206f776e657227732061646472657373206973206e756c6c60448201527f2e00000000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b600160a060020a03821615156114d9576040805160e560020a62461bcd02815260206004820152602660248201527f5265636f7665727920636f6e74726f6c6c65722773206164647265737320697360448201527f206e756c6c2e0000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b60008111611531576040805160e560020a62461bcd02815260206004820152601a60248201527f5265636f7665727920626c6f636b2064656c617920697320302e000000000000604482015290519081900360640190fd5b604080517f454950373132446f6d61696e28616464726573732064656c617965645265636f81527f766572794d6f64756c6529000000000000000000000000000000000000000000602080830191909152825191829003602b018220828201523082840152825180830384018152606090920190925280519101206002556001805473ffffffffffffffffffffffffffffffffffffffff19908116331790915560048054600160a060020a039586169083161790556003805493909416921691909117909155600555565b600354600160a060020a031681565b60025481565b60408051808201909152600581527f302e312e30000000000000000000000000000000000000000000000000000000602082015281565b604080517f52657365745265636f766572794f776e6572537472756374286164647265737381527f206f6c645265636f766572794f776e65722c61646472657373206e65775265636020808301919091527f6f766572794f776e65722900000000000000000000000000000000000000000082840152825191829003604b01822082820152600160a060020a03808616838501528416606080840191909152835180840390910181526080909201909252805191012060009061170a81611869565b949350505050565b6004546040805160008152602080820180845288905260ff85168284015260608201879052608082018690529151600160a060020a039093169260019260a0808401939192601f1981019281900390910190855afa158015611778573d6000803e3d6000fd5b50505060206040510351600160a060020a0316141515611808576040805160e560020a62461bcd02815260206004820152602560248201527f496e76616c6964207369676e617475726520666f72207265636f76657279206f60448201527f776e65722e000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b50505050565b604080516020808201879052600160a060020a0380871683850152808616606084015284166080808401919091528351808403909101815260a0909201909252805191012060009061185f81611869565b9695505050505050565b600254604080517f19000000000000000000000000000000000000000000000000000000000000006020808301919091527f01000000000000000000000000000000000000000000000000000000000000006021830152602282019390935260428082019490945281518082039094018452606201905281519101209056fe4f6e6c79207265636f7665727920636f6e74726f6c6c657220697320616c6c6f77656420746f2063616c6c2e0000000000000000000000000000000000000000a165627a7a72305820d7d2593cd6db88b8ed76cbb8b85e32c50134307556a83712fed6824ac6e6e3e30029 \ No newline at end of file diff --git a/lib/helper/User.js b/lib/helper/User.js index 593e382..0f9e30e 100644 --- a/lib/helper/User.js +++ b/lib/helper/User.js @@ -1,62 +1,210 @@ 'use strict'; const AbiBinProvider = require('../AbiBinProvider'); +const TxSender = require('../../utils/TxSender'); const UserWalletFactoryContractName = 'UserWalletFactory'; - const ProxyFactoryContractName = 'ProxyFactory'; - const THMasterCopyContractName = 'TokenHolder'; -const TxSender = require('../../utils/TxSender'); - /** - * This is used to create wallet of an user and configure it. + * Class is used to create a wallet of an user and configure it. */ class User { /** - * Constructor of User. + * @constructor * - * @param gnosisSafeMasterCopy The address of a master copy of gnosis safe contract. - * @param tokenHolderMasterCopy The address of a master copy of token holder contract. - * @param eip20Token The address of an EIP20Token of an economy. - * @param tokenRules The address of the token rules. - * @param userWalletFactoryAddress Address of UserWalletFactory contract. - * @param auxiliaryWeb3 Auxiliary chain web3 object. + * @param tokenHolderMasterCopy The address of a master copy of TokenHolder contract. + * @param gnosisSafeMasterCopy The address of a master copy of GnosisSafe contract. + * @param createAndAddModules The address of CreateAndAddModules contract. + * @param delayedRecoveryModuleMasterCopy The address of a master copy of + * DelayedRecoveryModule contract. + * @param eip20Token The address of EIP20Token contract of the economy. + * @param tokenRules The address of TokenRules contract. + * @param userWalletFactory The address of UserWalletFactory contract. + * @param proxyFactory The address of ProxyFactory contract. + * @param auxiliaryWeb3 Auxiliary chain's web3 object. */ constructor( - gnosisSafeMasterCopy, tokenHolderMasterCopy, + gnosisSafeMasterCopy, + delayedRecoveryModuleMasterCopy, + createAndAddModules, eip20Token, tokenRules, - userWalletFactoryAddress, + userWalletFactory, + proxyFactory, auxiliaryWeb3 ) { const oThis = this; - oThis.gnosisSafeMasterCopy = gnosisSafeMasterCopy; oThis.tokenHolderMasterCopy = tokenHolderMasterCopy; + oThis.gnosisSafeMasterCopy = gnosisSafeMasterCopy; + oThis.delayedRecoveryModuleMasterCopy = delayedRecoveryModuleMasterCopy; + oThis.createAndAddModules = createAndAddModules; oThis.eip20Token = eip20Token; oThis.tokenRules = tokenRules; - oThis.userWalletFactoryAddress = userWalletFactoryAddress; + oThis.userWalletFactory = userWalletFactory; + oThis.proxyFactory = proxyFactory; oThis.auxiliaryWeb3 = auxiliaryWeb3; - oThis.abiBinProvider = new AbiBinProvider(); } + /** + * Generates DelayedRecoveryModule::setup() function's executable data. + * + * @param recoveryOwnerAddress An address that signs the "recovery + * initiation/abortion" and "reset recovery owner" + * requests. + * @param recoveryControllerAddress An address that relays signed requests of + * different types. + * @param recoveryBlockDelay A required number of blocks to pass to + * be able to execute a recovery request. + * + * @returns {String} Executable data of the function. + */ + getDelayedRecoveryModuleSetupData(recoveryOwnerAddress, recoveryControllerAddress, recoveryBlockDelay) { + const oThis = this; + + return oThis.auxiliaryWeb3.eth.abi.encodeFunctionCall( + { + name: 'setup', + type: 'function', + inputs: [ + { + type: 'address', + name: 'recoveryOwnerAddress' + }, + { + type: 'address', + name: 'recoveryControllerAddress' + }, + { + type: 'uint256', + name: 'recoveryBlockDelay' + } + ] + }, + [recoveryOwnerAddress, recoveryControllerAddress, recoveryBlockDelay] + ); + } + + /** + * Generates ProxyFactory::createProxy() function's executable data to + * create DelayedRecoveryModule proxy. + * + * @param delayedRecoverySetupData Executable data of the DelayedRecoveryModule::setup() + * function. + * + * @returns {String} Executable data of the function. + */ + getDelayedRecoveryModuleCreationData(delayedRecoverySetupData) { + const oThis = this; + + return oThis.auxiliaryWeb3.eth.abi.encodeFunctionCall( + { + name: 'createProxy', + type: 'function', + inputs: [ + { + type: 'address', + name: 'delayedRecoveryModuleMasterCopy' + }, + { + type: 'bytes', + name: 'delayedRecoverySetupData' + } + ] + }, + [oThis.delayedRecoveryModuleMasterCopy, delayedRecoverySetupData] + ); + } + + /** + * Generates CreateAndAddModules::createAndAddModules() function's executable + * data to create and enable modules in GnosisSafe. + * + * @param {String{}} modulesCreationData An array of an executable data to create and enable + * modules within GnosisSafe during construction. + * + * @returns {String} Executable data of the function. + */ + getCreateAndAddModulesData(modulesCreationData) { + const oThis = this; + + const ModuleDataWrapper = new oThis.auxiliaryWeb3.eth.Contract([ + { + constant: false, + inputs: [ + { + name: 'data', + type: 'bytes' + } + ], + name: 'setup', + type: 'function' + } + ]); + + // Remove method id (10) and position of data in payload (64) + const reducedModulesCreationData = modulesCreationData.reduce( + (acc, data) => + acc + + ModuleDataWrapper.methods + .setup(data) + .encodeABI() + .substr(74), + '0x' + ); + + return oThis.auxiliaryWeb3.eth.abi.encodeFunctionCall( + { + name: 'createAndAddModules', + type: 'function', + inputs: [ + { + type: 'address', + name: 'proxyFactory' + }, + { + type: 'bytes', + name: 'data' + } + ] + }, + [oThis.proxyFactory, reducedModulesCreationData] + ); + } + /** * Generate the executable data for setup method of GnosisSafe contract. * * @param owners List of owners of the multisig contract for a user. * @param threshold Number of required confirmations for a Safe transaction. - * @param to Contract address for optional delegate call. - * @param data Data payload for optional delegate call. - * + * @param recoveryOwnerAddress An address that signs the "recovery + * initiation/abortion" and "reset recovery owner" + * requests. + * @param recoveryControllerAddress An address that relays signed requests of + * different types. + * @param recoveryBlockDelay A required number of blocks to pass to + * be able to execute a recovery request. * @returns {String} */ - getGnosisSafeData(owners, threshold, to, data) { + getGnosisSafeData(owners, threshold, recoveryOwnerAddress, recoveryControllerAddress, recoveryBlockDelay) { const oThis = this; + const delayedRecoveryModuleSetupData = oThis.getDelayedRecoveryModuleSetupData( + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay + ); + + const delayedRecoveryModuleCreationData = oThis.getDelayedRecoveryModuleCreationData( + delayedRecoveryModuleSetupData + ); + + const createAndAddModulesData = oThis.getCreateAndAddModulesData([delayedRecoveryModuleCreationData]); + return oThis.auxiliaryWeb3.eth.abi.encodeFunctionCall( { name: 'setup', @@ -80,7 +228,7 @@ class User { } ] }, - [owners, threshold, to, data] + [owners, threshold, oThis.createAndAddModules, createAndAddModulesData] ); } @@ -111,12 +259,18 @@ class User { } /** - * It is used for creation and configuration of gnosis safe and token holder proxy contract for user. + * It is used for creation and configuration of gnosis safe and token holder + * proxy contract for user. * * @param owners List of owners of the multisig. * @param threshold Number of required confirmations for a Safe transaction. - * @param to Contract address for optional delegate call. - * @param data Data payload for optional delegate call. + * @param recoveryOwnerAddress An address that signs the "recovery + * initiation/abortion" and "reset recovery owner" + * requests. + * @param recoveryControllerAddress An address that relays signed requests of + * different types. + * @param recoveryBlockDelay A required number of blocks to pass to + * be able to execute a recovery request. * @param sessionKeys Session key addresses to authorize. * @param sessionKeysSpendingLimits Session key's spending limits. * @param sessionKeysExpirationHeights Session key's expiration heights. @@ -127,8 +281,9 @@ class User { async createUserWallet( owners, threshold, - to, - data, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, sessionKeysSpendingLimits, sessionKeysExpirationHeights, @@ -139,14 +294,17 @@ class User { const txObject = oThis._createUserWalletRawTx( owners, threshold, - to, - data, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, sessionKeysSpendingLimits, sessionKeysExpirationHeights ); - const txReceipt = await new TxSender(txObject, oThis.auxiliaryWeb3, txOptions).execute(); + const txSender = new TxSender(txObject, oThis.auxiliaryWeb3, txOptions); + + const txReceipt = await txSender.execute(); return txReceipt; } @@ -187,12 +345,18 @@ class User { } /** - * Private method used for creation and configuration of gnosis safe and tokenholder contract for an user. + * Private method used for creation and configuration of gnosis safe and + * tokenholder contract for an user. * * @param owners List of owners of the multisig. * @param threshold Number of required confirmations for a Safe transaction. - * @param to Contract address for optional delegate call. - * @param data Data payload for optional delegate call. + * @param recoveryOwnerAddress An address that signs the "recovery + * initiation/abortion" and "reset recovery owner" + * requests. + * @param recoveryControllerAddress An address that relays signed requests of + * different types. + * @param recoveryBlockDelay A required number of blocks to pass to + * be able to execute a recovery request. * @param sessionKeys Session key addresses to authorize. * @param sessionKeysSpendingLimits Session key's spending limits. * @param sessionKeysExpirationHeights Session key's expiration heights. @@ -201,18 +365,26 @@ class User { _createUserWalletRawTx( owners, threshold, - to, - data, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, sessionKeysSpendingLimits, sessionKeysExpirationHeights ) { const oThis = this; - const gnosisSafeData = oThis.getGnosisSafeData(owners, threshold, to, data); + const gnosisSafeData = oThis.getGnosisSafeData( + owners, + threshold, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay + ); + + const jsonInterface = oThis.abiBinProvider.getABI(UserWalletFactoryContractName); - const jsonInterface = oThis.abiBinProvider.getABI(UserWalletFactoryContractName), - contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, oThis.userWalletFactoryAddress); + const contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, oThis.userWalletFactory); return contract.methods.createUserWallet( oThis.gnosisSafeMasterCopy, @@ -248,8 +420,9 @@ class User { sessionKeysExpirationHeights ); - const jsonInterface = oThis.abiBinProvider.getABI(ProxyFactoryContractName), - contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, proxyFactory); + const jsonInterface = oThis.abiBinProvider.getABI(ProxyFactoryContractName); + + const contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, proxyFactory); return contract.methods.createProxy(oThis.tokenHolderMasterCopy, thSetupExecutableData); } diff --git a/lib/setup/User.js b/lib/setup/User.js index bf70d62..c4b97df 100644 --- a/lib/setup/User.js +++ b/lib/setup/User.js @@ -1,12 +1,14 @@ 'use strict'; -const AbiBinProvider = require('./../AbiBinProvider'), - Deployer = require('./../../utils/DeployContract'); +const AbiBinProvider = require('./../AbiBinProvider'); +const Deployer = require('./../../utils/DeployContract'); -const MultiSigMasterCopyContractName = 'GnosisSafe', - THMasterCopyContractName = 'TokenHolder', - UserWalletFactoryContractName = 'UserWalletFactory', - ProxyFactoryContractName = 'ProxyFactory'; +const MultiSigMasterCopyContractName = 'GnosisSafe'; +const THMasterCopyContractName = 'TokenHolder'; +const UserWalletFactoryContractName = 'UserWalletFactory'; +const ProxyFactoryContractName = 'ProxyFactory'; +const CreateAndAddModulesContractName = 'CreateAndAddModules'; +const DelayedRecoveryModuleMasterCopyContractName = 'DelayedRecoveryModule'; /** * Performs setup and deployment tasks for user. @@ -21,6 +23,50 @@ class User { oThis.abiBinProvider = new AbiBinProvider(); } + /** + * Deploys DelayedRecoveryModule master copy. + * + * @param {Object} txOptions Transaction's options used for deploying. + * + * @returns {Object} Transaction receipt of the deployment. + */ + async deployDelayedRecoveryModuleMasterCopy(txOptions) { + const oThis = this; + + const txObject = oThis._deployDelayedRecoveryModuleMasterCopyRawTx(); + + const txReceipt = await new Deployer( + DelayedRecoveryModuleMasterCopyContractName, + txObject, + oThis.auxiliaryWeb3, + txOptions + ).deploy(); + + return txReceipt; + } + + /** + * Deploys Gnosis CreateAndAddModules contract. + * + * @param {Object} txOptions Transaction's options used for deploying. + * + * @returns {Object} Transaction receipt of the deployment. + */ + async deployCreateAndAddModules(txOptions) { + const oThis = this; + + const txObject = oThis._deployCreateAndAddModulesRawTx(); + + const txReceipt = await new Deployer( + CreateAndAddModulesContractName, + txObject, + oThis.auxiliaryWeb3, + txOptions + ).deploy(); + + return txReceipt; + } + /** * Deploys gnosis MultiSig master copy contract. * @@ -99,6 +145,47 @@ class User { return txReceipt; } + /** + * Creates and returns a transaction object to deploy DelayedRecoveryModule + * master copy. + * + * @returns {Object} Transaction object to deploy. + */ + _deployDelayedRecoveryModuleMasterCopyRawTx() { + const oThis = this; + + const { abiBinProvider } = oThis; + const jsonInterface = abiBinProvider.getABI(DelayedRecoveryModuleMasterCopyContractName); + const bin = abiBinProvider.getBIN(DelayedRecoveryModuleMasterCopyContractName); + + const contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, null); + + return contract.deploy({ + data: bin, + arguments: [] + }); + } + + /** + * Creates and returns a transaction object to deploy CreateAndAddModules. + * + * @returns {Object} Transaction object to deploy. + */ + _deployCreateAndAddModulesRawTx() { + const oThis = this; + + const { abiBinProvider } = oThis; + const jsonInterface = abiBinProvider.getABI(CreateAndAddModulesContractName); + const bin = abiBinProvider.getBIN(CreateAndAddModulesContractName); + + const contract = new oThis.auxiliaryWeb3.eth.Contract(jsonInterface, null); + + return contract.deploy({ + data: bin, + arguments: [] + }); + } + /** * Private method which Deploys gnosis MultiSig master copy contract. * diff --git a/package.json b/package.json index 9ec96f5..83dfe75 100644 --- a/package.json +++ b/package.json @@ -22,35 +22,40 @@ "url": "https://github.com/OpenSTFoundation/openst.js/issues" }, "scripts": { - "test": "mocha --recursive --timeout 120000 test --exit", + "test": "mocha --recursive --timeout 1200000 test --exit", "pre-commit": "lint-staged" }, "dependencies": { "@openstfoundation/mosaic-tbd": "https://github.com/OpenSTFoundation/mosaic-tbd.git" }, "devDependencies": { - "chai": "4.1.2", - "mocha": "5.0.0", "@babel/core": "7.1.0", "@babel/polyfill": "7.0.0", "@babel/preset-env": "7.1.0", "@babel/runtime": "7.0.0", + "abi-decoder": "1.2.0", "babel-loader": "8.0.2", + "chai": "4.1.2", "commander": "2.8.1", "edit-json-file": "1.0.8", + "eslint": "^5.13.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.16.0", "expose-loader": "0.7.5", "lint-staged": "7.2.0", + "mocha": "5.0.0", "npm-run-all": "4.1.3", "os": "0.1.1", + "package-json-cleanup-loader": "1.0.3", "pre-commit": "1.2.2", "prettier": "1.13.7", "string-replace-loader": "2.1.1", "webpack": "4.19.1", "webpack-cli": "3.1.0", "webpack-uglify-js-plugin": "1.1.9", - "package-json-cleanup-loader": "1.0.3", - "abi-decoder": "1.2.0", - "wait-port": "0.2.2" + "wait-port": "0.2.2", + "assert": "^1.4.1", + "ethereumjs-util": "^6.1.0" }, "pre-commit": { "run": [ diff --git a/test/integration/DelayedRecovery.js b/test/integration/DelayedRecovery.js new file mode 100644 index 0000000..8bb5f2c --- /dev/null +++ b/test/integration/DelayedRecovery.js @@ -0,0 +1,388 @@ +// Copyright 2019 OpenST Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const { assert } = require('chai'); +const EthUtils = require('ethereumjs-util'); +const Web3 = require('web3'); + +const { dockerSetup, dockerTeardown } = require('./../../utils/docker'); + +const Mosaic = require('@openstfoundation/mosaic-tbd'); +const ConfigReader = require('../utils/configReader'); +const UserSetup = require('../../lib/setup/User.js'); +const User = require('../../lib/helper/User.js'); +const MockContractsDeployer = require('../utils/MockContractsDeployer.js'); +const TokenRulesSetup = require('../../lib/setup/TokenRules.js'); +const AbiBinProvider = require('../../lib/AbiBinProvider.js'); + +const { OrganizationHelper } = Mosaic.ChainSetup; + +const abiBinProvider = new AbiBinProvider(); + +const GNOSIS_SAFE_CONTRACT_NAME = 'GnosisSafe'; +const DELAYED_RECOVERY_MODULE_CONTRACT_NAME = 'DelayedRecoveryModule'; + +class WalletProvider { + async init(walletCount) { + const wallets = await auxiliaryWeb3.eth.accounts.wallet; + + this.index = wallets.length; + this.walletCount = walletCount; + + await auxiliaryWeb3.eth.accounts.wallet.create(this.index + this.walletCount); + + this.wallets = await auxiliaryWeb3.eth.accounts.wallet; + } + + get() { + const wallet = this.wallets[this.index]; + this.index += 1; + return wallet; + } + + getAddress() { + const wallet = this.get(); + return wallet.address; + } +} + +let auxiliaryWeb3 = {}; +let walletProvider = {}; +let userFactory = {}; +let deployerAddress = ''; +let recoveryControllerAddress = ''; + +async function createUserWallet( + userFactoryInstance, + owners, + threshold, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay +) { + const txOptions = { + from: deployerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }; + + const createUserWalletTxResponse = await userFactoryInstance.createUserWallet( + owners, + threshold, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, + [], + [], + [], + txOptions + ); + + const { returnValues } = createUserWalletTxResponse.events.UserWalletCreated; + const userWalletEvent = JSON.parse(JSON.stringify(returnValues)); + + const tokenHolderProxyAddress = userWalletEvent._tokenHolderProxy; + const gnosisSafeProxyAddress = userWalletEvent._gnosisSafeProxy; + + return { + tokenHolderProxyAddress, + gnosisSafeProxyAddress + }; +} + +function instantiateGnosisSafeProxy(gnosisSafeProxyAddress) { + const gnosisSafeAbi = abiBinProvider.getABI(GNOSIS_SAFE_CONTRACT_NAME); + + const gnosisSafeProxy = new auxiliaryWeb3.eth.Contract(gnosisSafeAbi, gnosisSafeProxyAddress); + + return gnosisSafeProxy; +} + +function instantiateRecoveryModuleProxy(recoveryModuleAddressProxy) { + const recoveryModuleAbi = abiBinProvider.getABI(DELAYED_RECOVERY_MODULE_CONTRACT_NAME); + + const recoveryModule = new auxiliaryWeb3.eth.Contract(recoveryModuleAbi, recoveryModuleAddressProxy); + + return recoveryModule; +} + +function hashRecoveryModuleDomainSeparator(recoveryModuleAddress) { + const RECOERY_MODULE_DOMAIN_SEPARATOR_TYPEHASH = auxiliaryWeb3.utils.keccak256( + 'EIP712Domain(address delayedRecoveryModule)' + ); + + return auxiliaryWeb3.utils.keccak256( + auxiliaryWeb3.eth.abi.encodeParameters( + ['bytes32', 'address'], + [RECOERY_MODULE_DOMAIN_SEPARATOR_TYPEHASH, recoveryModuleAddress] + ) + ); +} + +function hashRecoveryModuleRecoveryStruct(structTypeHash, prevOwner, oldOwner, newOwner) { + return auxiliaryWeb3.utils.keccak256( + auxiliaryWeb3.eth.abi.encodeParameters( + ['bytes32', 'address', 'address', 'address'], + [structTypeHash, prevOwner, oldOwner, newOwner] + ) + ); +} + +function hashRecoveryModuleRecovery(recoveryModuleAddress, structTypeHash, prevOwner, oldOwner, newOwner) { + const recoveryStructHash = hashRecoveryModuleRecoveryStruct(structTypeHash, prevOwner, oldOwner, newOwner); + + const domainSeparatorHash = hashRecoveryModuleDomainSeparator(recoveryModuleAddress); + + return auxiliaryWeb3.utils.soliditySha3( + { t: 'bytes1', v: '0x19' }, + { t: 'bytes1', v: '0x01' }, + { t: 'bytes32', v: domainSeparatorHash }, + { t: 'bytes32', v: recoveryStructHash } + ); +} + +function signRecovery(recoveryModuleAddress, structTypeHash, prevOwner, oldOwner, newOwner, recoveryOwnerPrivateKey) { + const recoveryHash = hashRecoveryModuleRecovery(recoveryModuleAddress, structTypeHash, prevOwner, oldOwner, newOwner); + + const signature = EthUtils.ecsign(EthUtils.toBuffer(recoveryHash), EthUtils.toBuffer(recoveryOwnerPrivateKey)); + + return { + recoveryHash, + signature + }; +} + +function signInitiateRecovery(recoveryModuleAddress, prevOwner, oldOwner, newOwner, recoveryOwnerPrivateKey) { + const INITIATE_RECOVERY_STRUCT_TYPEHASH = auxiliaryWeb3.utils.keccak256( + 'InitiateRecoveryStruct(address prevOwner,address oldOwner,address newOwner)' + ); + + return signRecovery( + recoveryModuleAddress, + INITIATE_RECOVERY_STRUCT_TYPEHASH, + prevOwner, + oldOwner, + newOwner, + recoveryOwnerPrivateKey + ); +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function waitBlockNumber(blockNumber) { + let currentBlockNumber = await auxiliaryWeb3.eth.getBlockNumber(); + + while (currentBlockNumber < blockNumber) { + // eslint-disable-next-line no-await-in-loop + await sleep(200); + currentBlockNumber = await auxiliaryWeb3.eth.getBlockNumber(); + } +} + +async function initiateAndExecuteRecovery( + userFactoryInstance, + owners, + threshold, + recoveryOwner, + recoveryControllerAddress, + recoveryBlockDelay, + prevOwnerAddress, + oldOwnerAddress, + newOwnerAddress +) { + const { gnosisSafeProxyAddress } = await createUserWallet( + userFactoryInstance, + owners, + threshold, + recoveryOwner.address, + recoveryControllerAddress, + recoveryBlockDelay + ); + + const gnosisSafeProxy = instantiateGnosisSafeProxy(gnosisSafeProxyAddress); + + const txOptions = { + from: deployerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }; + + const modules = await gnosisSafeProxy.methods.getModules().call(txOptions); + + assert.strictEqual(modules.length, 1); + + const recoveryModuleProxyAddress = modules[0]; + + const recoveryModule = instantiateRecoveryModuleProxy(recoveryModuleProxyAddress); + + const { signature } = signInitiateRecovery( + recoveryModuleProxyAddress, + prevOwnerAddress, + oldOwnerAddress, + newOwnerAddress, + recoveryOwner.privateKey + ); + + await recoveryModule.methods + .initiateRecovery( + prevOwnerAddress, + oldOwnerAddress, + newOwnerAddress, + EthUtils.bufferToHex(signature.r), + EthUtils.bufferToHex(signature.s), + signature.v + ) + .send({ + from: recoveryControllerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }); + + const blockNumber = await auxiliaryWeb3.eth.getBlockNumber(); + + await waitBlockNumber(blockNumber + recoveryBlockDelay); + + await recoveryModule.methods.executeRecovery(prevOwnerAddress, oldOwnerAddress, newOwnerAddress).send({ + from: recoveryControllerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }); + + return gnosisSafeProxy; +} + +describe('Delayed Recovery', async () => { + before(async () => { + const { rpcEndpointOrigin } = await dockerSetup(); + + auxiliaryWeb3 = new Web3(rpcEndpointOrigin); + + const accountsOrigin = await auxiliaryWeb3.eth.getAccounts(); + + recoveryControllerAddress = accountsOrigin[0]; + deployerAddress = accountsOrigin[1]; + + walletProvider = new WalletProvider(); + await walletProvider.init(50); + + const organizationWorkerAddress = walletProvider.getAddress(); + + const userSetup = new UserSetup(auxiliaryWeb3); + + const txOptions = { + from: deployerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }; + + const proxyFactoryDeployTxResponse = await userSetup.deployProxyFactory(txOptions); + const proxyFactoryAddress = proxyFactoryDeployTxResponse.receipt.contractAddress; + + const userWalletFactoryDeployTxResponse = await userSetup.deployUserWalletFactory(txOptions); + const userWalletFactoryAddress = userWalletFactoryDeployTxResponse.receipt.contractAddress; + + const gnosisSafeMasterCopyDeployTxResponse = await userSetup.deployMultiSigMasterCopy(txOptions); + const gnosisSafeMasterCopyAddress = gnosisSafeMasterCopyDeployTxResponse.receipt.contractAddress; + + const createAndAddModulesDeployTxResponse = await userSetup.deployCreateAndAddModules(txOptions); + const createAndAddModulesAddress = createAndAddModulesDeployTxResponse.receipt.contractAddress; + + const delayedRecoveryModuleMasterCopyDeployTxResponse = await userSetup.deployDelayedRecoveryModuleMasterCopy( + txOptions + ); + const delayedRecoveryModuleMasterCopyAddress = + delayedRecoveryModuleMasterCopyDeployTxResponse.receipt.contractAddress; + + const mockTokenDeployerInstance = new MockContractsDeployer(deployerAddress, auxiliaryWeb3); + await mockTokenDeployerInstance.deployMockToken(auxiliaryWeb3, txOptions); + const mockToken = mockTokenDeployerInstance.addresses.MockToken; + + const organizationOwnerAddress = deployerAddress; + const orgHelper = new OrganizationHelper(auxiliaryWeb3, null); + const orgConfig = { + deployer: deployerAddress, + owner: organizationOwnerAddress, + workers: organizationWorkerAddress, + workerExpirationHeight: '20000000' + }; + await orgHelper.setup(orgConfig); + const organizationAddress = orgHelper.address; + + const tokenRulesSetup = await new TokenRulesSetup(auxiliaryWeb3); + const tokenRulesDeployTxResponse = await tokenRulesSetup.deploy(organizationAddress, mockToken, txOptions); + const tokenRulesAddress = tokenRulesDeployTxResponse.receipt.contractAddress; + + const tokenHolderMasterCopyDeployTxResponse = await userSetup.deployTokenHolderMasterCopy(txOptions); + const tokenHolderMasterCopyAddress = tokenHolderMasterCopyDeployTxResponse.receipt.contractAddress; + + userFactory = new User( + tokenHolderMasterCopyAddress, + gnosisSafeMasterCopyAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, + mockToken, + tokenRulesAddress, + userWalletFactoryAddress, + proxyFactoryAddress, + auxiliaryWeb3 + ); + }); + + after(() => { + dockerTeardown(); + }); + + it('Initiates recovery, waits required block number to proceed, executes.', async () => { + const txOptions = { + from: deployerAddress, + gasPrice: ConfigReader.gasPrice, + gas: ConfigReader.gas + }; + + const ownerAddress1 = walletProvider.getAddress(); + const ownerAddress2 = walletProvider.getAddress(); + const ownerAddress3 = walletProvider.getAddress(); + + const threshold = 1; + + const recoveryOwner = walletProvider.get(); + const recoveryBlockDelay = 5; + + const newOwnerAddress = walletProvider.getAddress(); + + const owners = [ownerAddress1, ownerAddress2, ownerAddress3]; + + const gnosisSafeProxy = await initiateAndExecuteRecovery( + userFactory, + owners, + threshold, + recoveryOwner, + recoveryControllerAddress, + recoveryBlockDelay, + ownerAddress1, // prev owner + ownerAddress2, // old owner + newOwnerAddress + ); + + assert.isOk(await gnosisSafeProxy.methods.isOwner(ownerAddress1).call(txOptions)); + assert.isOk(await gnosisSafeProxy.methods.isOwner(ownerAddress3).call(txOptions)); + assert.isOk(await gnosisSafeProxy.methods.isOwner(newOwnerAddress).call(txOptions)); + + assert.isNotOk(await gnosisSafeProxy.methods.isOwner(ownerAddress2).call(txOptions)); + }); +}); diff --git a/test/integration/DirectTransfer.js b/test/integration/DirectTransfer.js index c52c79c..b469bd1 100644 --- a/test/integration/DirectTransfer.js +++ b/test/integration/DirectTransfer.js @@ -26,6 +26,8 @@ let txOptions, gnosisSafeMasterCopyAddress, proxyFactoryAddress, tokenRulesAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, pricerRuleAddress, worker, organization, @@ -108,6 +110,25 @@ describe('Direct transfers between TH contracts', async function() { ); }); + it('Deploys DelayedRecoveryModule master copy.', async function() { + const userSetup = new UserSetup(auxiliaryWeb3); + const txResponse = await userSetup.deployDelayedRecoveryModuleMasterCopy(txOptions); + delayedRecoveryModuleMasterCopyAddress = txResponse.receipt.contractAddress; + assert.strictEqual(txResponse.receipt.status, true); + assert.isNotNull( + delayedRecoveryModuleMasterCopyAddress, + "DelayedRecoveryModule master copy contract's address is null." + ); + }); + + it('Deploys CreateAndAddModules.', async function() { + const userSetup = new UserSetup(auxiliaryWeb3); + const txResponse = await userSetup.deployCreateAndAddModules(txOptions); + createAndAddModulesAddress = txResponse.receipt.contractAddress; + assert.strictEqual(txResponse.receipt.status, true); + assert.isNotNull(createAndAddModulesAddress, "createAndAddModules contract's address is null."); + }); + it('Deploys Gnosis MultiSig MasterCopy contract', async function() { const userSetup = new UserSetup(auxiliaryWeb3); const multiSigTxResponse = await userSetup.deployMultiSigMasterCopy(txOptions); @@ -191,26 +212,34 @@ describe('Direct transfers between TH contracts', async function() { it('Creates first user wallet', async function() { txOptions.from = deployerAddress; const userInstance = new User( - gnosisSafeMasterCopyAddress, thMasterCopyAddress, + gnosisSafeMasterCopyAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, mockToken, tokenRulesAddress, userWalletFactoryAddress, + proxyFactoryAddress, auxiliaryWeb3 ); - await auxiliaryWeb3.eth.accounts.wallet.create(1); + await auxiliaryWeb3.eth.accounts.wallet.create(10); ephemeralKey = auxiliaryWeb3.eth.accounts.wallet[0]; const owners = [owner], threshold = 1, sessionKeys = [ephemeralKey.address]; + const recoveryOwnerAddress = auxiliaryWeb3.eth.accounts.wallet[7].address; + const recoveryControllerAddress = auxiliaryWeb3.eth.accounts.wallet[8].address; + const recoveryBlockDelay = 10; + const response = await userInstance.createUserWallet( owners, threshold, - config.NULL_ADDRESS, - config.ZERO_BYTES, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, [config.sessionKeySpendingLimit], [config.sessionKeyExpirationHeight], @@ -229,26 +258,34 @@ describe('Direct transfers between TH contracts', async function() { it('Should create second user wallet', async function() { const userInstance = new User( - gnosisSafeMasterCopyAddress, thMasterCopyAddress, + gnosisSafeMasterCopyAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, mockToken, tokenRulesAddress, userWalletFactoryAddress, + proxyFactoryAddress, auxiliaryWeb3 ); - await auxiliaryWeb3.eth.accounts.wallet.create(1); + await auxiliaryWeb3.eth.accounts.wallet.create(10); ephemeralKey = auxiliaryWeb3.eth.accounts.wallet[0]; const owners = [owner], threshold = 1, sessionKeys = [ephemeralKey.address]; + const recoveryOwnerAddress = auxiliaryWeb3.eth.accounts.wallet[7].address; + const recoveryControllerAddress = auxiliaryWeb3.eth.accounts.wallet[8].address; + const recoveryBlockDelay = 10; + const response = await userInstance.createUserWallet( owners, threshold, - config.NULL_ADDRESS, - config.ZERO_BYTES, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, [config.sessionKeySpendingLimit], [config.sessionKeyExpirationHeight], @@ -267,11 +304,14 @@ describe('Direct transfers between TH contracts', async function() { it('Creates a company wallet', async function() { const userInstance = new User( - null, thMasterCopyAddress, + null, // gnosis safe master copy address + null, // delayed recovery module master copy address + null, // create and add modules contract address mockToken, tokenRulesAddress, userWalletFactoryAddress, + null, // proxy factory address auxiliaryWeb3 ); @@ -305,18 +345,18 @@ describe('Direct transfers between TH contracts', async function() { contract = new auxiliaryWeb3.eth.Contract(mockTokenAbi, mockToken, txOptions); // Funding TH proxy with tokens. - const amount = config.tokenHolderBalance, - txObject = contract.methods.transfer(tokenHolderSender, amount); + const amount = config.tokenHolderBalance; + const txObject = contract.methods.transfer(tokenHolderSender, amount); await txObject.send(txOptions); - const initialTHProxyBalance = await contract.methods.balanceOf(tokenHolderSender).call(), - transferTos = [tokenHolderFirstReceiver, tokenHolderSecondReceiver], - firstReceiverInitialBalance = await contract.methods.balanceOf(tokenHolderFirstReceiver).call(), - secondReceiverInitialBalance = await contract.methods.balanceOf(tokenHolderSecondReceiver).call(), - transferAmounts = [20, 10]; + const initialTHProxyBalance = await contract.methods.balanceOf(tokenHolderSender).call(); + const transferTos = [tokenHolderFirstReceiver, tokenHolderSecondReceiver]; + const firstReceiverInitialBalance = await contract.methods.balanceOf(tokenHolderFirstReceiver).call(); + const secondReceiverInitialBalance = await contract.methods.balanceOf(tokenHolderSecondReceiver).call(); + const transferAmounts = [20, 10]; - const directTransferExecutable = tokenRulesObject.getDirectTransferExecutableData(transferTos, transferAmounts), - nonce = 0; + const directTransferExecutable = tokenRulesObject.getDirectTransferExecutableData(transferTos, transferAmounts); + const nonce = 0; let transaction = { from: tokenHolderSender, diff --git a/test/integration/WalletOperations.js b/test/integration/WalletOperations.js index 02cf627..bd1d58f 100644 --- a/test/integration/WalletOperations.js +++ b/test/integration/WalletOperations.js @@ -27,6 +27,8 @@ let auxiliaryWeb3, thMasterCopyAddress, gnosisSafeMasterCopyAddress, tokenRulesAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, worker, organization, mockToken, @@ -103,6 +105,25 @@ describe('Wallet operations', async function() { ); }); + it('Deploys DelayedRecoveryModule master copy.', async function() { + const userSetup = new UserSetup(auxiliaryWeb3); + const txResponse = await userSetup.deployDelayedRecoveryModuleMasterCopy(txOptions); + delayedRecoveryModuleMasterCopyAddress = txResponse.receipt.contractAddress; + assert.strictEqual(txResponse.receipt.status, true); + assert.isNotNull( + delayedRecoveryModuleMasterCopyAddress, + "DelayedRecoveryModule master copy contract's address is null." + ); + }); + + it('Deploys CreateAndAddModules.', async function() { + const userSetup = new UserSetup(auxiliaryWeb3); + const txResponse = await userSetup.deployCreateAndAddModules(txOptions); + createAndAddModulesAddress = txResponse.receipt.contractAddress; + assert.strictEqual(txResponse.receipt.status, true); + assert.isNotNull(createAndAddModulesAddress, "createAndAddModules contract's address is null."); + }); + it('Should deploy Gnosis MultiSig MasterCopy contract', async function() { const userSetup = new UserSetup(auxiliaryWeb3); const multiSigTxResponse = await userSetup.deployMultiSigMasterCopy(txOptions); @@ -124,19 +145,28 @@ describe('Wallet operations', async function() { assert.strictEqual(userWalletFactoryResponse.receipt.status, true); }); - // // wallet3, wallet9 are the owners. - // wallet1 and wallet2 are the owners. + it('Should deploy ProxyFactory contract', async function() { + const userSetup = new UserSetup(auxiliaryWeb3); + const proxyFactoryResponse = await userSetup.deployProxyFactory(txOptions); + proxyFactoryAddress = proxyFactoryResponse.receipt.contractAddress; + assert.strictEqual(proxyFactoryResponse.receipt.status, true); + }); + + // wallet3, wallet9 are the owners. it('Should create a user wallet', async function() { - await auxiliaryWeb3.eth.accounts.wallet.create(3); + await auxiliaryWeb3.eth.accounts.wallet.create(10); ephemeralKey = auxiliaryWeb3.eth.accounts.wallet[0]; const userInstance = new User( - gnosisSafeMasterCopyAddress, thMasterCopyAddress, + gnosisSafeMasterCopyAddress, + delayedRecoveryModuleMasterCopyAddress, + createAndAddModulesAddress, mockToken, tokenRulesAddress, userWalletFactoryAddress, + proxyFactoryAddress, auxiliaryWeb3 ); @@ -146,11 +176,16 @@ describe('Wallet operations', async function() { sessionKeysSpendingLimits = [config.sessionKeySpendingLimit], sessionKeysExpirationHeights = [config.sessionKeyExpirationHeight]; + const recoveryOwnerAddress = auxiliaryWeb3.eth.accounts.wallet[7].address; + const recoveryControllerAddress = auxiliaryWeb3.eth.accounts.wallet[8].address; + const recoveryBlockDelay = 10; + const response = await userInstance.createUserWallet( owners, threshold, - config.NULL_ADDRESS, - config.ZERO_BYTES, + recoveryOwnerAddress, + recoveryControllerAddress, + recoveryBlockDelay, sessionKeys, sessionKeysSpendingLimits, sessionKeysExpirationHeights, diff --git a/utils/TxSender.js b/utils/TxSender.js index 1748e26..4be63e3 100644 --- a/utils/TxSender.js +++ b/utils/TxSender.js @@ -28,25 +28,19 @@ class TxSender { async execute() { const oThis = this; - let receipt = null, - transactionHash = null; + let transactionHash = ''; - await oThis.txObject + return oThis.txObject .send(oThis.txOptions) - .on('receipt', function(value) { - receipt = value; + .on('transactionHash', function(transactionHash) { + console.log('transactionHash = ' + transactionHash); }) - .on('transactionHash', function(value) { - console.log('transaction hash: ' + value); - transactionHash = value; + .on('receipt', function(receipt) { + console.log(`Transaction (${transactionHash}) consumed ${receipt.gasUsed} gas.`); }) - .on('error', function(error) { - return Promise.reject(error); + .on('error', (error) => { + console.log(`Transaction (${transactionHash}) failed: ${error}`); }); - - console.log('Gas used : ', receipt.gasUsed); - - return receipt; } }