diff --git a/contracts/AddressRegistry.sol b/contracts/AddressRegistry.sol deleted file mode 100644 index eb100e8..0000000 --- a/contracts/AddressRegistry.sol +++ /dev/null @@ -1,62 +0,0 @@ -pragma solidity ^0.5.0; - - -contract AddressRegistry { - event AddressSet(string name, address addr); - event DefaultLogicSet(address logicAddr); - event LogicSet(address logicAddr, bool isLogic); - - mapping(bytes32 => address) registry; - mapping(address => bool) public defaultLogicProxies; - mapping(address => bool) public logicProxies; - - constructor() public { - registry[keccak256(abi.encodePacked("admin"))] = msg.sender; - registry[keccak256(abi.encodePacked("owner"))] = msg.sender; - } - - function getAddress(string memory name) public view returns(address) { - return registry[keccak256(abi.encodePacked(name))]; - } - - function setAddress(string memory name, address addr) public { - require( - msg.sender == getAddress("admin") || - msg.sender == getAddress("owner"), - "Permission Denied" - ); - registry[keccak256(abi.encodePacked(name))] = addr; - emit AddressSet(name, addr); - } - - modifier isAdmin() { - require( - msg.sender == getAddress("admin"), - "Permission Denied" - ); - _; - } - - function getLogic(address logicAddr) public view returns (bool) { - if (defaultLogicProxies[logicAddr]) { - return true; - } else if (logicProxies[logicAddr]) { - return true; - } else { - return false; - } - } - - function setDefaultLogic(address logicAddr) public isAdmin { - require(msg.sender == getAddress("admin"), "Permission Denied"); - defaultLogicProxies[logicAddr] = true; - emit DefaultLogicSet(logicAddr); - } - - function setLogic(address logicAddr, bool isLogic) public isAdmin { - require(msg.sender == getAddress("admin"), "Permission Denied"); - logicProxies[logicAddr] = true; - emit LogicSet(logicAddr, isLogic); - } - -} \ No newline at end of file diff --git a/contracts/ProxyRegistry.sol b/contracts/ProxyRegistry.sol deleted file mode 100644 index e468e08..0000000 --- a/contracts/ProxyRegistry.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.5.0; - -import "./UserProxy.sol"; - - -contract ProxyRegistry { - event Created(address indexed sender, address indexed owner, address proxy); - mapping(address => UserProxy) public proxies; - address public logicProxyAddr; - - constructor(address logicProxyAddr_) public { - logicProxyAddr = logicProxyAddr_; - } - - function build(uint activeDays) public returns (UserProxy proxy) { - proxy = build(msg.sender, activeDays); - } - - // deploys a new proxy instance and sets custom owner of proxy - function build(address owner, uint activeDays) public returns (UserProxy proxy) { - require( - proxies[owner] == UserProxy(0) || proxies[owner].owner() != owner, - "multiple-proxy-per-user-not-allowed" - ); // Not allow new proxy if the user already has one and remains being the owner - - proxy = new UserProxy(logicProxyAddr, activeDays); - emit Created(msg.sender, owner, address(proxy)); - proxy.setOwner(owner); - proxies[owner] = proxy; - } -} diff --git a/contracts/Registry.sol b/contracts/Registry.sol new file mode 100644 index 0000000..9866078 --- /dev/null +++ b/contracts/Registry.sol @@ -0,0 +1,146 @@ +pragma solidity ^0.5.0; + +import "./UserWallet.sol"; + + +/** + * @title Address Registry + */ +contract AddressRegistry { + event LogSetAddress(string name, address addr); + + mapping(bytes32 => address) registry; + + constructor() public { + registry[keccak256(abi.encodePacked("admin"))] = msg.sender; + registry[keccak256(abi.encodePacked("owner"))] = msg.sender; + } + + /** + * @dev get the address from system registry + */ + function getAddress(string memory name) public view returns(address) { + return registry[keccak256(abi.encodePacked(name))]; + } + + /** + * @dev set new address in system registry + */ + function setAddress(string memory name, address addr) public { + require( + msg.sender == getAddress("admin") || + msg.sender == getAddress("owner"), + "permission-denied" + ); + registry[keccak256(abi.encodePacked(name))] = addr; + emit LogSetAddress(name, addr); + } + + modifier isAdmin() { + require(msg.sender == getAddress("admin"), "permission-denied"); + _; + } + +} + + +/** + * @title Logic Registry + */ +contract LogicRegistry is AddressRegistry { + + event LogSetDefaultLogic(address logicAddr); + event LogSetLogic(address logicAddr, bool isLogic); + + mapping(address => bool) public defaultLogicProxies; + mapping(address => bool) public logicProxies; + + /** + * @dev get the boolean of the logic proxy contract + * @param logicAddr is the logic proxy address + */ + function getLogic(address logicAddr) public view returns (bool) { + if (defaultLogicProxies[logicAddr]) { + return true; + } else if (logicProxies[logicAddr]) { + return true; + } else { + return false; + } + } + + /** + * @dev this sets the default logic proxy to true + * @param logicAddr is the default logic proxy address + */ + function setDefaultLogic(address logicAddr) public isAdmin { + defaultLogicProxies[logicAddr] = true; + emit LogSetDefaultLogic(logicAddr); + } + + /** + * @dev this updates the boolean of the logic proxy + * @param logicAddr is the logic proxy address + * @param isLogic is the boolean to set for the logic proxy + */ + function setLogic(address logicAddr, bool isLogic) public isAdmin { + logicProxies[logicAddr] = true; + emit LogSetLogic(logicAddr, isLogic); + } + +} + + +/** + * @title User Wallet Registry + */ +contract ProxyRegistry is LogicRegistry { + + event Created(address indexed sender, address indexed owner, address proxy); + + mapping(address => UserWallet) public proxies; + bool public guardianEnabled; + + /** + * @dev deploys a new proxy instance and sets msg.sender as owner of proxy + */ + function build() public returns (UserWallet proxy) { + proxy = build(msg.sender); + } + + /** + * @dev deploys a new proxy instance and sets custom owner of proxy + * Throws if the owner already have a UserWallet + */ + function build(address owner) public returns (UserWallet proxy) { + require(proxies[owner] == UserWallet(0), "multiple-proxy-per-user-not-allowed"); + proxy = new UserWallet(owner); + emit Created(msg.sender, owner, address(proxy)); + proxies[owner] = proxy; + } + + /** + * @dev update the proxy record whenever owner changed on any proxy + * Throws if msg.sender is not a proxy contract created via this contract + */ + function updateProxyRecord(address currentOwner, address nextOwner) public { + require(msg.sender == address(proxies[currentOwner]), "invalid-proxy-or-owner"); + proxies[nextOwner] = proxies[currentOwner]; + proxies[currentOwner] = UserWallet(0); + } + + /** + * @dev enable guardian in overall system + */ + function enableGuardian() public isAdmin { + guardianEnabled = true; + } + + /** + * @dev disable guardian in overall system + */ + function disableGuardian() public isAdmin { + guardianEnabled = false; + } + +} \ No newline at end of file diff --git a/contracts/UserProxy.sol b/contracts/UserProxy.sol deleted file mode 100644 index 485cc3e..0000000 --- a/contracts/UserProxy.sol +++ /dev/null @@ -1,142 +0,0 @@ -pragma solidity ^0.5.0; - - -library SafeMath { - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "math-not-safe"); - return c; - } -} - - -contract UserAuth { - using SafeMath for uint; - using SafeMath for uint256; - - event LogSetOwner(address indexed owner, bool isGuardian); - event LogSetGuardian(address indexed guardian); - - mapping(uint => address) public guardians; - address public owner; - uint public lastActivity; // timestamp - // guardians can set owner after owner stay inactive for certain period - uint public activePeriod; // timestamp - - constructor() public { - owner = msg.sender; - emit LogSetOwner(msg.sender, false); - } - - modifier auth { - require(isAuth(msg.sender), "permission-denied"); - _; - } - - function setOwner(address owner_) public auth { - owner = owner_; - emit LogSetOwner(owner, false); - } - - function setOwnerViaGuardian(address owner_, uint num) public { - require(msg.sender == guardians[num], "permission-denied"); - require(block.timestamp > lastActivity.add(activePeriod), "active-period-not-over"); - owner = owner_; - emit LogSetOwner(owner, true); - } - - function setGuardian(uint num, address guardian_) public auth { - require(num > 0 && num < 6, "guardians-cant-exceed-five"); - guardians[num] = guardian_; - emit LogSetGuardian(guardian_); - } - - function isAuth(address src) internal view returns (bool) { - if (src == address(this)) { - return true; - } else if (src == owner) { - return true; - } else { - return false; - } - } -} - - -contract UserNote { - event LogNote( - bytes4 indexed sig, - address indexed guy, - bytes32 indexed foo, - bytes32 bar, - uint wad, - bytes fax - ); - - modifier note { - bytes32 foo; - bytes32 bar; - assembly { - foo := calldataload(4) - bar := calldataload(36) - } - emit LogNote( - msg.sig, - msg.sender, - foo, - bar, - msg.value, - msg.data - ); - _; - } -} - - -interface AddressRegistry { - function getLogic(address logicAddr) external view returns (bool); -} - - -// checking if the logic proxy is authorised -contract UserLogic { - address public logicProxyAddr; - function isLogicAuthorised(address logicAddr) internal view returns (bool) { - AddressRegistry logicProxy = AddressRegistry(logicProxyAddr); - return logicProxy.getLogic(logicAddr); - } -} - - - -contract UserProxy is UserAuth, UserNote, UserLogic { - constructor(address _logicProxyAddr, uint _activePeriod) public { - logicProxyAddr = _logicProxyAddr; - lastActivity = block.timestamp; - activePeriod = _activePeriod; - } - - function() external payable {} - - function execute(address _target, bytes memory _data) public payable auth note returns (bytes memory response) { - require(_target != address(0), "user-proxy-target-address-required"); - require(isLogicAuthorised(_target), "logic-proxy-address-not-allowed"); - lastActivity = block.timestamp; - // call contract in current context - assembly { - let succeeded := delegatecall(sub(gas, 5000), _target, add(_data, 0x20), mload(_data), 0, 0) - let size := returndatasize - - response := mload(0x40) - mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - mstore(response, size) - returndatacopy(add(response, 0x20), 0, size) - - switch iszero(succeeded) - case 1 { - // throw if delegatecall failed - revert(add(response, 0x20), size) - } - } - } -} diff --git a/contracts/UserWallet.sol b/contracts/UserWallet.sol new file mode 100644 index 0000000..ad1a2f1 --- /dev/null +++ b/contracts/UserWallet.sol @@ -0,0 +1,283 @@ +pragma solidity ^0.5.0; + + +/** + * @dev because math is not safe + */ +library SafeMath { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "math-not-safe"); + return c; + } +} + + +/** + * @title AddressRegistryInterface Interface + */ +interface AddressRegistryInterface { + function getLogic(address logicAddr) external view returns (bool); + function updateProxyRecord(address currentOwner, address nextOwner) external; + function guardianEnabled() external returns (bool); +} + + +/** + * @title Address Record + */ +contract AddressRecord { + + /** + * @dev address registry of system, logic and proxy addresses + */ + address public registry; + + /** + * @dev this updates the internal proxy ownership on "registry" contract + * @param currentOwner is the current owner + * @param nextOwner is the new assigned owner + */ + function setProxyRecordOwner(address currentOwner, address nextOwner) internal { + AddressRegistryInterface initCall = AddressRegistryInterface(registry); + initCall.updateProxyRecord(currentOwner, nextOwner); + } + + /** + * @param logicAddr is the logic proxy contract address + * @return the true boolean for logic proxy if authorised otherwise false + */ + function isLogicAuthorised(address logicAddr) internal view returns (bool) { + AddressRegistryInterface logicProxy = AddressRegistryInterface(registry); + return logicProxy.getLogic(logicAddr); + } + +} + + +/** + * @title User Auth + */ +contract UserAuth is AddressRecord { + using SafeMath for uint; + using SafeMath for uint256; + + event LogSetOwner(address indexed owner, address setter); + event LogSetPendingOwner(address indexed pendingOwner, address setter); + address public owner; + address public pendingOwner; + uint public claimOnwershipTime; // now + 7 days + uint public gracePeriod; // to set the new owner + + /** + * @dev defines the "proxy registry" contract and sets the owner + */ + constructor() public { + gracePeriod = 3 days; + } + + /** + * @dev Throws if not called by owner or contract itself + */ + modifier auth { + require(isAuth(msg.sender), "permission-denied"); + _; + } + + /** + * @dev sets the "pending owner" and provide 3 days grace period to set the new owner via setOwner() + * Throws if called before 10 (i.e. 7 + 3) day after assigning "pending owner" + * @param nextOwner is the assigned "pending owner" + */ + function setPendingOwner(address nextOwner) public auth { + require(block.timestamp > claimOnwershipTime.add(gracePeriod), "owner-is-still-pending"); + pendingOwner = nextOwner; + claimOnwershipTime = block.timestamp.add(7 days); + emit LogSetPendingOwner(nextOwner, msg.sender); + } + + /** + * @dev sets "pending owner" as real owner + * Throws if no "pending owner" + * Throws if called before 7 day after assigning "pending owner" + */ + function setOwner() public { + require(pendingOwner != address(0), "no-pending-address"); + require(block.timestamp > claimOnwershipTime, "owner-is-still-pending"); + setProxyRecordOwner(owner, pendingOwner); + owner = pendingOwner; + pendingOwner = address(0); + emit LogSetOwner(owner, msg.sender); + } + + /** + * @dev checks if called by owner or contract itself + * @param src is the address initiating the call + */ + function isAuth(address src) internal view returns (bool) { + if (src == address(this)) { + return true; + } else if (src == owner) { + return true; + } else { + return false; + } + } + +} + + +/** + * @title User Guardians + */ +contract UserGuardian is UserAuth { + + event LogSetGuardian(address indexed guardian); + event LogNewActivePeriod(uint newActivePeriod); + event LogSetOwnerViaGuardian(address nextOwner, address indexed guardian); + + mapping(uint => address) public guardians; + uint public lastActivity; // time when called "execute" last time + uint public activePeriod; // the period over lastActivity when guardians have no rights + + /** + * @dev Throws if guardians not enabled by system admin + */ + modifier guard() { + AddressRegistryInterface initCall = AddressRegistryInterface(registry); + require(initCall.guardianEnabled(), "guardian-not-enabled"); + _; + } + + /** + * @dev guardians can set "owner" after owner stay inactive for minimum "activePeriod" + * @param nextOwner is the new owner + * @param num is the assigned guardian number + */ + function setOwnerViaGuardian(address nextOwner, uint num) public guard { + require(msg.sender == guardians[num], "permission-denied"); + require(block.timestamp > lastActivity.add(activePeriod), "active-period-not-over"); + owner = nextOwner; + emit LogSetOwnerViaGuardian(nextOwner, guardians[num]); + } + + /** + * @dev sets the guardian with assigned number (upto 3) + * @param num is the guardian assigned number + * @param _guardian is the new guardian address + */ + function setGuardian(uint num, address _guardian) public auth guard { + require(num > 0 && num < 4, "guardians-cant-exceed-three"); + guardians[num] = _guardian; + emit LogSetGuardian(_guardian); + } + + /** + * @dev sets the guardian with assigned number (upto 3) + * @param _activePeriod is the period when guardians have no rights to dethrone the owner + */ + function updateActivePeriod(uint _activePeriod) public auth guard { + activePeriod = _activePeriod; + emit LogNewActivePeriod(_activePeriod); + } + +} + + +/** + * @dev logging the execute events + */ +contract UserNote { + event LogNote( + bytes4 indexed sig, + address indexed guy, + bytes32 indexed foo, + bytes32 bar, + uint wad, + bytes fax + ); + + modifier note { + bytes32 foo; + bytes32 bar; + assembly { + foo := calldataload(4) + bar := calldataload(36) + } + emit LogNote( + msg.sig, + msg.sender, + foo, + bar, + msg.value, + msg.data + ); + _; + } +} + + +/** + * @title User Owned Contract Wallet + */ +contract UserWallet is UserGuardian, UserNote { + + event LogExecute(address target, uint srcNum, uint sessionNum); + + /** + * @dev sets the "address registry", owner's last activity, owner's active period and initial owner + * @param _owner initial owner of the contract + * @param _logicRegistryAddr address registry address which have logic proxy registry + */ + constructor(address _owner) public { + registry = msg.sender; + owner = _owner; + lastActivity = block.timestamp; + activePeriod = 30 days; // default and changeable + } + + function() external payable {} + + /** + * @dev execute authorised calls via delegate call + * @param _target logic proxy address + * @param _data delegate call data + * @param srcNum to find the source + * @param sessionNum to find the session + */ + function execute( + address _target, + bytes memory _data, + uint srcNum, + uint sessionNum + ) + public + payable + auth + note + returns (bytes memory response) + { + require(_target != address(0), "user-proxy-target-address-required"); + require(isLogicAuthorised(_target), "logic-proxy-address-not-allowed"); + lastActivity = block.timestamp; + emit LogExecute(_target, srcNum, sessionNum); + + // call contract in current context + assembly { + let succeeded := delegatecall(sub(gas, 5000), _target, add(_data, 0x20), mload(_data), 0, 0) + let size := returndatasize + + response := mload(0x40) + mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + mstore(response, size) + returndatacopy(add(response, 0x20), 0, size) + + switch iszero(succeeded) + case 1 { + // throw if delegatecall failed + revert(add(response, 0x20), size) + } + } + } + +}