snt-gas-relay/test-dapp/contracts/identity/IdentityGasRelay.sol

519 lines
16 KiB
Solidity

pragma solidity ^0.4.21;
import "./Identity.sol";
import "../common/MessageSigned.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 MSG_CALL_PREFIX = bytes4(keccak256("callGasRelay(address,uint256,bytes32,uint256,uint256,address)"));
bytes4 public constant MSG_DEPLOY_PREFIX = bytes4(keccak256("deployGasRelay(uint256,bytes32,uint256,uint256,address)"));
bytes4 public constant MSG_APPROVEANDCALL_PREFIX = bytes4(keccak256("approveAndCallGasRelay(address,address,uint256,bytes32,uint256,uint256)"));
event ExecutedGasRelayed(bytes32 messageHash);
event ContractDeployed(address deployedAddress);
/**
* @param _messageHash that is signed
* @param _requiredPurpose key purpose for this type of call
* @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
*/
modifier executeGasRelayed (
bytes32 _messageHash,
uint256 _requiredPurpose,
uint _nonce,
uint _gasPrice,
uint _gasLimit,
address _gasToken,
bytes _messageSignatures
) {
//query current gas available
uint startGas = gasleft();
//verify transaction parameters
require(startGas >= _gasLimit);
require(_nonce == nonce);
//verify if signatures are valid and came from correct actors;
verifySignatures(
_requiredPurpose,
_messageHash,
_messageSignatures
);
//increase nonce
nonce++;
//executes transaction
_;
//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);
}
}
}
constructor(
bytes32[] _keys,
uint256[] _purposes,
uint256[] _types,
uint256 _managerThreshold,
uint256 _actorThreshold,
address _recoveryContract
)
Identity(
_keys,
_purposes,
_types,
_managerThreshold,
_actorThreshold,
_recoveryContract
)
public
{
}
/**
* @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
returns (bool success)
{
//query current gas available
uint startGas = gasleft();
//verify transaction parameters
//require(startGas >= _gasLimit); // TODO: Tune this
require(_nonce == nonce);
//verify if signatures are valid and came from correct actors;
verifySignatures(
_to == address(this) ? MANAGEMENT_KEY : ACTION_KEY,
callGasRelayHash(
_to,
_value,
keccak256(_data),
_nonce,
_gasPrice,
_gasLimit,
_gasToken
),
_messageSignatures
);
//increase nonce
nonce++;
//executes transaction
success = _commitCall(_nonce, _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 deploys contract in return of gas proportional amount multiplied by `_gasPrice` of `_gasToken`
* allows identity of being controlled without requiring ether in key balace
* @param _value call value (ether) to be sent to newly created contract
* @param _data contract code 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 deployGasRelayed(
uint256 _value,
bytes _data,
uint _nonce,
uint _gasPrice,
uint _gasLimit,
address _gasToken,
bytes _messageSignatures
)
external
returns(address deployedAddress)
{
//query current gas available
uint startGas = gasleft();
//verify transaction parameters
require(startGas >= _gasLimit);
require(_nonce == nonce);
//verify if signatures are valid and came from correct actors;
verifySignatures(
ACTION_KEY,
deployGasRelayHash(
_value,
keccak256(_data),
_nonce,
_gasPrice,
_gasLimit,
_gasToken
),
_messageSignatures
);
//increase nonce
nonce++;
deployedAddress = doCreate(_value, _data);
emit ContractDeployed(deployedAddress);
//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 (in `_baseToken`)
* @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
returns(bool success)
{
//query current gas available
uint startGas = gasleft();
//verify transaction parameters
// require(startGas >= _gasLimit); // TODO: tune this
require(_nonce == nonce);
//verify if signatures are valid and came from correct actors;
verifySignatures(
ACTION_KEY,
deployGasRelayHash(
_value,
keccak256(_data),
_nonce,
_gasPrice,
_gasLimit,
_gasToken
),
_messageSignatures
);
//increase nonce
nonce++;
require(_baseToken != address(0)); //_baseToken should be something!
require(_to != address(this)); //no management with approveAndCall
require(_to != address(0)); //need valid destination
ERC20Token(_baseToken).approve(_to, _value);
success = _commitCall(_nonce, _to, 0, _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 _messageHash message hash provided for the payload
* @param _messageSignatures ethereum signed `getSignHash(_messageHash)` message
* @return true case valid
*/
function verifySignatures(
uint256 _requiredKey,
bytes32 _messageHash,
bytes _messageSignatures
)
public
view
returns(bool)
{
// calculates signHash
bytes32 signHash = getSignHash(_messageHash);
uint _amountSignatures = _messageSignatures.length / 65;
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(keyHasPurpose(_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),
MSG_CALL_PREFIX,
_to,
_value,
_dataHash,
_nonce,
_gasPrice,
_gasLimit,
_gasToken
);
}
/**
* @notice get callHash
* @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 deployGasRelayHash(
uint256 _value,
bytes32 _dataHash,
uint256 _nonce,
uint256 _gasPrice,
uint256 _gasLimit,
address _gasToken
)
public
view
returns (bytes32 _callGasRelayHash)
{
_callGasRelayHash = keccak256(
address(this),
MSG_DEPLOY_PREFIX,
_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),
MSG_APPROVEANDCALL_PREFIX,
_baseToken,
_to,
_value,
_dataHash,
_nonce,
_gasPrice,
_gasLimit,
_gasToken
);
}
/**
* @notice recovers key 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 keccak256(
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
internal
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);
}
/**
* @notice creates new contract based on input `_code` and transfer `_value` ETH to this instance
* @param _value amount ether in wei to sent to deployed address at its initialization
* @param _code contract code
*/
function doCreate(
uint _value,
bytes _code
)
internal
returns (address createdContract)
{
bool failed;
assembly {
createdContract := create(_value, add(_code, 0x20), mload(_code))
failed := iszero(extcodesize(createdContract))
}
require(!failed);
}
}