diff --git a/contracts/identity/IdentityGasRelay.sol b/contracts/identity/IdentityGasRelay.sol new file mode 100644 index 0000000..b02b284 --- /dev/null +++ b/contracts/identity/IdentityGasRelay.sol @@ -0,0 +1,364 @@ +pragma solidity ^0.4.21; + +import "./Identity.sol"; +import "../token/ERC20Token.sol"; + +/** + * @title IdentityGasRelay + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @notice enables economic abstraction for Identity + */ +contract IdentityGasRelay is Identity { + + bytes4 public constant CALL_PREFIX = bytes4(keccak256("callGasRelay(address,uint256,bytes32,uint256,uint256,address)")); + bytes4 public constant APPROVEANDCALL_PREFIX = bytes4(keccak256("approveAndCallGasRelay(address,address,uint256,bytes32,uint256,uint256)")); + + event ExecutedGasRelayed(bytes32 signHash, bool success); + + /** + * @notice include ethereum signed callHash in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken` + * allows identity of being controlled without requiring ether in key balace + * @param _to destination of call + * @param _value call value (ether) + * @param _data call data + * @param _nonce current identity nonce + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasLimit minimal gasLimit required to execute this call + * @param _gasToken token being used for paying `msg.sender` + * @param _messageSignatures rsv concatenated ethereum signed message signatures required + */ + function callGasRelayed( + address _to, + uint256 _value, + bytes _data, + uint _nonce, + uint _gasPrice, + uint _gasLimit, + address _gasToken, + bytes _messageSignatures + ) + external + { + uint startGas = gasleft(); + //verify transaction parameters + require(startGas >= _gasLimit); + require(_nonce == nonce); + // calculates signHash + bytes32 signHash = getSignHash( + callGasRelayHash( + _to, + _value, + keccak256(_data), + _nonce, + _gasPrice, + _gasLimit, + _gasToken + ) + ); + + //verify if signatures are valid and came from correct actors; + verifySignatures( + _to == address(this) ? MANAGEMENT_KEY : ACTION_KEY, + signHash, + _messageSignatures + ); + + //executes transaction + nonce++; + bool success = _to.call.value(_value)(_data); + emit ExecutedGasRelayed( + signHash, + success + ); + + //refund gas used using contract held ERC20 tokens or ETH + if (_gasPrice > 0) { + uint256 _amount = 21000 + (startGas - gasleft()); + _amount = _amount * _gasPrice; + if (_gasToken == address(0)) { + address(msg.sender).transfer(_amount); + } else { + ERC20Token(_gasToken).transfer(msg.sender, _amount); + } + } + } + + /** + * @notice include ethereum signed approve ERC20 and call hash + * (`ERC20Token(baseToken).approve(_to, _value)` + `_to.call(_data)`). + * in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken` + * fixes race condition in double transaction for ERC20. + * @param _baseToken token approved for `_to` + * @param _to destination of call + * @param _value call value (ether) + * @param _data call data + * @param _nonce current identity nonce + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasLimit minimal gasLimit required to execute this call + * @param _gasToken token being used for paying `msg.sender` + * @param _messageSignatures rsv concatenated ethereum signed message signatures required + */ + function approveAndCallGasRelayed( + address _baseToken, + address _to, + uint256 _value, + bytes _data, + uint _nonce, + uint _gasPrice, + uint _gasLimit, + address _gasToken, + bytes _messageSignatures + ) + external + { + uint startGas = gasleft(); + //verify transaction parameters + require(startGas >= _gasLimit); + require(_nonce == nonce); + require(_baseToken != address(0)); //_baseToken should be something! + require(_to != address(this)); //no management with approveAndCall + + // calculates signHash + bytes32 signHash = getSignHash( + approveAndCallGasRelayHash( + _baseToken, + _to, + _value, + keccak256(_data), + _nonce, + _gasPrice, + _gasLimit, + _gasToken + ) + ); + + //verify if signatures are valid and came from correct actors; + verifySignatures( + ACTION_KEY, //no management with approveAndCall + signHash, + _messageSignatures + ); + + approveAndCall( + signHash, + _baseToken, + _to, + _value, + _data + ); + + //refund gas used using contract held ERC20 tokens or ETH + if (_gasPrice > 0) { + uint256 _amount = 21000 + (startGas - gasleft()); + _amount = _amount * _gasPrice; + if (_gasToken == address(0)) { + address(msg.sender).transfer(_amount); + } else { + ERC20Token(_gasToken).transfer(msg.sender, _amount); + } + } + + } + + /** + * @notice reverts if signatures are not valid for the signed hash and required key type. + * @param _requiredKey key required to call, if _to from payload is the identity itself, is `MANAGEMENT_KEY`, else `ACTION_KEY` + * @param _signHash ethereum signable callGasRelayHash message provided for the payload + * @param _messageSignatures ethereum signed `_signHash` messages + * @return true case valid + */ + function verifySignatures( + uint256 _requiredKey, + bytes32 _signHash, + bytes _messageSignatures + ) + public + view + returns(bool) + { + uint _amountSignatures = _messageSignatures.length / 72; + require(_amountSignatures == purposeThreshold[_requiredKey]); + bytes32 _lastKey = 0; + for (uint256 i = 0; i < _amountSignatures; i++) { + bytes32 _currentKey = recoverKey( + _signHash, + _messageSignatures, + i + ); + require(_currentKey > _lastKey); //assert keys are different + require(isKeyPurpose(_currentKey, _requiredKey)); + _lastKey = _currentKey; + } + return true; + } + + + /** + * @notice get callHash + * @param _to destination of call + * @param _value call value (ether) + * @param _dataHash call data hash + * @param _nonce current identity nonce + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasLimit minimal gasLimit required to execute this call + * @param _gasToken token being used for paying `msg.sender` + * @return callGasRelayHash the hash to be signed by wallet + */ + function callGasRelayHash( + address _to, + uint256 _value, + bytes32 _dataHash, + uint _nonce, + uint256 _gasPrice, + uint256 _gasLimit, + address _gasToken + ) + public + view + returns (bytes32 _callGasRelayHash) + { + _callGasRelayHash = keccak256( + address(this), + CALL_PREFIX, + _to, + _value, + _dataHash, + _nonce, + _gasPrice, + _gasLimit, + _gasToken + ); + } + + + /** + * @notice get callHash + * @param _to destination of call + * @param _value call value (ether) + * @param _dataHash call data hash + * @param _nonce current identity nonce + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasLimit minimal gasLimit required to execute this call + * @param _gasToken token being used for paying `msg.sender` + * @return callGasRelayHash the hash to be signed by wallet + */ + function approveAndCallGasRelayHash( + address _baseToken, + address _to, + uint256 _value, + bytes32 _dataHash, + uint _nonce, + uint256 _gasPrice, + uint256 _gasLimit, + address _gasToken + ) + public + view + returns (bytes32 _callGasRelayHash) + { + _callGasRelayHash = keccak256( + address(this), + APPROVEANDCALL_PREFIX, + _baseToken, + _to, + _value, + _dataHash, + _nonce, + _gasPrice, + _gasLimit, + _gasToken + ); + } + + /** + * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` + * @param _hash Sign to hash. + * @return signHash Hash ethereum wallet signs. + */ + function getSignHash( + bytes32 _hash + ) + pure + public + returns(bytes32 signHash) + { + signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); + } + + /** + * @notice recovers address who signed the message + * @param _signHash operation ethereum signed message hash + * @param _messageSignature message `_signHash` signature + * @param _pos which signature to read + */ + function recoverKey ( + bytes32 _signHash, + bytes _messageSignature, + uint256 _pos + ) + pure + public + returns(bytes32) + { + uint8 v; + bytes32 r; + bytes32 s; + (v,r,s) = signatureSplit(_messageSignature, _pos); + return bytes32( + ecrecover( + _signHash, + v, + r, + s + ) + ); + } + + /** + * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s` + * @param _pos which signature to read + * @param _signatures concatenated vrs signatures + */ + function signatureSplit(bytes _signatures, uint256 _pos) + pure + public + returns (uint8 v, bytes32 r, bytes32 s) + { + uint pos = _pos + 1; + // The signature format is a compact form of: + // {bytes32 r}{bytes32 s}{uint8 v} + // Compact means, uint8 is not padded to 32 bytes. + assembly { + r := mload(add(_signatures, mul(32,pos))) + s := mload(add(_signatures, mul(64,pos))) + // Here we are loading the last 32 bytes, including 31 bytes + // of 's'. There is no 'mload8' to do this. + // + // 'byte' is not working due to the Solidity parser, so lets + // use the second best option, 'and' + v := and(mload(add(_signatures, mul(65,pos))), 0xff) + } + + require(v == 27 || v == 28); + } + + function approveAndCall( + bytes32 _signHash, + address _token, + address _to, + uint256 _value, + bytes _data + ) + private + { + //executes transaction + nonce++; + ERC20Token(_token).approve(_to, _value); + emit ExecutedGasRelayed( + _signHash, + _to.call(_data) + ); + + } + +} \ No newline at end of file diff --git a/contracts/status/SNTController.sol b/contracts/status/SNTController.sol new file mode 100644 index 0000000..f65e7ab --- /dev/null +++ b/contracts/status/SNTController.sol @@ -0,0 +1,298 @@ +pragma solidity ^0.4.17; + +import "../token/TokenController.sol"; +import "../common/Owned.sol"; +import "../token/ERC20Token.sol"; +import "../token/MiniMeToken.sol"; + +/** + * @title SNTController + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @notice enables economic abstraction for SNT + */ +contract SNTController is TokenController, Owned { + + + bytes4 public constant TRANSFER_PREFIX = bytes4(keccak256("transferSNT(address,uint256,uint256,uint256)")); + bytes4 public constant EXECUTE_PREFIX = bytes4(keccak256("executeGasRelayed(address,bytes,uint256,uint256,uint256)")); + + MiniMeToken public snt; + mapping (address => uint256) public signNonce; + mapping (address => bool) public allowPublicExecution; + + event PublicExecutionEnabled(address indexed contractAddress, bool enabled); + event GasRelayedExecution(address indexed msgSigner, bytes32 signedHash, bool executed); + event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount); + event ControllerChanged(address indexed _newController); + + /** + * @notice Constructor + * @param _owner Authority address + * @param _snt SNT token + */ + function SNTController(address _owner, address _snt) public { + owner = _owner; + snt = MiniMeToken(_snt); + } + + /** + * @notice allows externally owned address sign a message to transfer SNT and pay + * @param _to address receving the tokens from message signer + * @param _amount total being transfered + * @param _nonce current signNonce of message signer + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _signature concatenated rsv of message + */ + function transferSNT( + address _to, + uint256 _amount, + uint256 _nonce, + uint256 _gasPrice, + bytes _signature + ) + external + { + uint256 startGas = gasleft(); + bytes32 msgSigned = getSignHash( + getTransferSNTHash( + _to, + _amount, + _nonce, + _gasPrice + ) + ); + + address msgSigner = recoverAddress(msgSigned, _signature); + require(signNonce[msgSigner] == _nonce); + signNonce[msgSigner]++; + if (snt.transferFrom(msgSigner, _to, _amount)) { + require(snt.transferFrom(msgSigner, msg.sender, (21000 + startGas-gasleft()) * _gasPrice)); + } + } + + /** + * @notice allows externally owned address sign a message to offer SNT for a execution + * @param _allowedContract address of a contracts in execution trust list; + * @param _data msg.data to be sent to `_allowedContract` + * @param _nonce current signNonce of message signer + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasMinimal minimal amount of gas needed to complete the execution + * @param _signature concatenated rsv of message + */ + function executeGasRelayed( + address _allowedContract, + bytes _data, + uint256 _nonce, + uint256 _gasPrice, + uint256 _gasMinimal, + bytes _signature + ) + external + { + uint256 startGas = gasleft(); + require(startGas >= _gasMinimal); + bytes32 msgSigned = getSignHash( + getExecuteGasRelayedHash( + _allowedContract, + _data, + _nonce, + _gasPrice, + _gasMinimal + ) + ); + + address msgSigner = recoverAddress(msgSigned, _signature); + require(signNonce[msgSigner] == _nonce); + signNonce[msgSigner]++; + bool success = _allowedContract.call(_data); + emit GasRelayedExecution(msgSigner, msgSigned, success); + require( + snt.transferFrom( + msgSigner, + msg.sender, + (21000 + startGas-gasleft()) * _gasPrice + ) + ); + } + + /** + * @notice The owner of this contract can change the controller of the SNT token + * Please, be sure that the owner is a trusted agent or 0x0 address. + * @param _newController The address of the new controller + */ + function changeController(address _newController) public onlyOwner { + snt.changeController(_newController); + emit ControllerChanged(_newController); + } + + function enablePublicExecution(address _contract, bool _enable) public onlyOwner { + allowPublicExecution[_contract] = _enable; + emit PublicExecutionEnabled(_contract, _enable); + } + + ////////// + // Safety Methods + ////////// + + /** + * @notice This method can be used by the controller to extract mistakenly + * sent tokens to this contract. + * @param _token The address of the token contract that you want to recover + * set to 0 in case you want to extract ether. + */ + function claimTokens(address _token) public onlyOwner { + if (snt.controller() == address(this)) { + snt.claimTokens(_token); + } + if (_token == 0x0) { + address(owner).transfer(this.balance); + return; + } + + ERC20Token token = ERC20Token(_token); + uint256 balance = token.balanceOf(this); + token.transfer(owner, balance); + emit ClaimedTokens(_token, owner, balance); + } + + + ////////// + // MiniMe Controller Interface functions + ////////// + + // In between the offering and the network. Default settings for allowing token transfers. + function proxyPayment(address) public payable returns (bool) { + return false; + } + + function onTransfer(address, address, uint256) public returns (bool) { + return true; + } + + function onApprove(address, address, uint256) public returns (bool) { + return true; + } + + /** + * @notice get execution hash + * @param _allowedContract address of a contracts in execution trust list; + * @param _data msg.data to be sent to `_allowedContract` + * @param _nonce current signNonce of message signer + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + * @param _gasMinimal minimal amount of gas needed to complete the execution + */ + function getExecuteGasRelayedHash( + address _allowedContract, + bytes _data, + uint256 _nonce, + uint256 _gasPrice, + uint256 _gasMinimal + ) + public + view + returns (bytes32 execHash) + { + execHash = keccak256( + address(this), + EXECUTE_PREFIX, + _allowedContract, + keccak256(_data), + _nonce, + _gasPrice, + _gasMinimal + ); + } + + /** + * @notice get transfer hash + * @param _to address receving the tokens from message signer + * @param _amount total being transfered + * @param _nonce current signNonce of message signer + * @param _gasPrice price in SNT paid back to msg.sender for each gas unit used + */ + function getTransferSNTHash( + address _to, + uint256 _amount, + uint256 _nonce, + uint256 _gasPrice + ) + public + view + returns (bytes32 txHash) + { + txHash = keccak256( + address(this), + TRANSFER_PREFIX, + _to, + _amount, + _nonce, + _gasPrice + ); + } + + /** + * @notice recovers address who signed the message + * @param _signHash operation ethereum signed message hash + * @param _messageSignature message `_signHash` signature + */ + function recoverAddress( + bytes32 _signHash, + bytes _messageSignature + ) + pure + public + returns(address) + { + uint8 v; + bytes32 r; + bytes32 s; + (v,r,s) = signatureSplit(_messageSignature); + return ecrecover( + _signHash, + v, + r, + s + ); + } + + /** + * @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s` + */ + function signatureSplit(bytes _signature) + pure + public + returns (uint8 v, bytes32 r, bytes32 s) + { + // The signature format is a compact form of: + // {bytes32 r}{bytes32 s}{uint8 v} + // Compact means, uint8 is not padded to 32 bytes. + assembly { + r := mload(add(_signature, 32)) + s := mload(add(_signature, 64)) + // Here we are loading the last 32 bytes, including 31 bytes + // of 's'. There is no 'mload8' to do this. + // + // 'byte' is not working due to the Solidity parser, so lets + // use the second best option, 'and' + v := and(mload(add(_signature, 65)), 0xff) + } + + require(v == 27 || v == 28); + } + + /** + * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` + * @param _hash Sign to hash. + * @return signHash Hash to be signed. + */ + function getSignHash( + bytes32 _hash + ) + pure + public + returns (bytes32 signHash) + { + signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash); + } + +} \ No newline at end of file