diff --git a/contracts/identity/IdentityGasRelay.sol b/contracts/identity/IdentityGasRelay.sol index 5254cee..2651282 100644 --- a/contracts/identity/IdentityGasRelay.sol +++ b/contracts/identity/IdentityGasRelay.sol @@ -11,11 +11,12 @@ import "../token/ERC20Token.sol"; 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` + * @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) @@ -83,6 +84,82 @@ contract IdentityGasRelay is Identity { } } + /** + * @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 _gasMinimal minimal amount of gas needed to complete the execution + * @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 _gasMinimal, + address _gasToken, + bytes _messageSignatures + ) + external + { + //verify transaction parameters + require(_nonce == nonce); + uint startGas = gasleft(); + require(startGas >= _gasMinimal); + 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, + _gasMinimal, + _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. @@ -154,6 +231,46 @@ contract IdentityGasRelay is Identity { ); } + + /** + * @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 _gasMinimal minimal amount of gas needed to complete the execution + * @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 _gasMinimal, + address _gasToken + ) + public + view + returns (bytes32 _callGasRelayHash) + { + _callGasRelayHash = keccak256( + address(this), + APPROVEANDCALL_PREFIX, + _baseToken, + _to, + _value, + _dataHash, + _nonce, + _gasPrice, + _gasMinimal, + _gasToken + ); + } + /** * @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"` * @param _hash Sign to hash. @@ -226,4 +343,23 @@ contract IdentityGasRelay is Identity { 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