diff --git a/contracts/status/SNTController.sol b/contracts/status/SNTController.sol new file mode 100644 index 0000000..3af76ca --- /dev/null +++ b/contracts/status/SNTController.sol @@ -0,0 +1,297 @@ +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 + */ +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