pragma solidity ^0.5.11;
// Libraries
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address payable public owner;
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(owner);
owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(address payable _newOwner) public onlyOwner {
_transferOwnership(_newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param _newOwner The address to transfer ownership to.
*/
function _transferOwnership(address payable _newOwner) internal {
require(_newOwner != address(0));
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() public onlyOwner whenNotPaused {
paused = true;
emit Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() public onlyOwner whenPaused {
paused = false;
emit Unpause();
}
}
/**
* @title Elliptic curve signature operations
* @dev Based on https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
* TODO Remove this library once solidity supports passing a signature to ecrecover.
* See https://github.com/ethereum/solidity/issues/864
*/
library ECRecovery {
/**
* @dev Recover signer address from a message by using their signature
* @param _hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
* @param _sig bytes signature, the signature is generated using web3.eth.sign()
*/
function recover(bytes32 _hash, bytes memory _sig)
internal
pure
returns (address)
{
bytes32 r;
bytes32 s;
uint8 v;
// Check the signature length
if (_sig.length != 65) {
return (address(0));
}
// Divide the signature in r, s and v variables
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solium-disable-next-line security/no-inline-assembly
assembly {
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}
// If the version is correct return the signer address
if (v != 27 && v != 28) {
return (address(0));
} else {
// solium-disable-next-line arg-overflow
return ecrecover(_hash, v, r, s);
}
}
/**
* toEthSignedMessageHash
* @dev prefix a bytes32 value with "\x19Ethereum Signed Message:"
* and hash the result
*/
function toEthSignedMessageHash(bytes32 _hash)
internal
pure
returns (bytes32)
{
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)
);
}
}
////// End Libraries
////// ETHDenver Staking Contract
contract ETHDenverStaking is Ownable, Pausable {
using ECRecovery for bytes32;
event UserStake(address userFortmaticAddress, address walletAddress, uint amountStaked);
event UserRecoupStake(address userFortmaticAddress, address walletAddress, uint amountStaked);
// Debug events
event debugBytes32(bytes32 _msg);
event debugBytes(bytes _msg);
event debugString(string _msg);
event debugAddress(address _address);
// ETHDenver will need to authorize staking and recouping.
address public grantSigner;
// End of the event, when staking can be sweeped
uint public finishDate;
// fortmaticAddress => walletAddress
mapping (address => address payable) public userStakedAddress;
// ETH amount staked by a given fortmaticAddress
mapping (address => uint) public stakedAmount;
constructor(address _grantSigner, uint _finishDate) public {
require(_grantSigner != address(0x0));
require(_finishDate > block.timestamp);
grantSigner = _grantSigner;
finishDate = _finishDate;
}
// Public functions
// function allow the staking for a participant
function stake(address _userFortmaticAddress, uint _expiryDate, bytes memory _signature) public payable whenNotPaused {
bytes32 hashMessage = keccak256(abi.encodePacked(_userFortmaticAddress, msg.value, _expiryDate));
address signer = hashMessage.toEthSignedMessageHash().recover(_signature);
require(signer == grantSigner, "Signature is not valid");
require(block.timestamp < _expiryDate, "Grant is expired");
require(userStakedAddress[_userFortmaticAddress] == address(0x0), "User has already staked!");
userStakedAddress[_userFortmaticAddress] = msg.sender;
stakedAmount[_userFortmaticAddress] = msg.value;
emit UserStake(_userFortmaticAddress, msg.sender, msg.value);
}
// function allow the staking for a participant
function recoupStake(address _userFortmaticAddress, uint _expiryDate, bytes memory _signature) public whenNotPaused {
bytes32 hashMessage = keccak256(abi.encodePacked(_userFortmaticAddress, _expiryDate));
address signer = hashMessage.toEthSignedMessageHash().recover(_signature);
require(signer == grantSigner, "Signature is not valid");
require(block.timestamp < _expiryDate, "Grant is expired");
require(userStakedAddress[_userFortmaticAddress] != address(0x0), "User has not staked!");
address payable stakedBy = userStakedAddress[_userFortmaticAddress];
uint amount = stakedAmount[_userFortmaticAddress];
userStakedAddress[_userFortmaticAddress] = address(0x0);
stakedAmount[_userFortmaticAddress] = 0;
stakedBy.transfer(amount);
emit UserRecoupStake(_userFortmaticAddress, stakedBy, amount);
}
// Owner functions
function setGrantSigner(address _signer) public onlyOwner {
require(_signer != address(0x0), "address is null");
grantSigner = _signer;
}
function sweepStakes() public onlyOwner {
require(block.timestamp > finishDate, "EthDenver is not over yet!");
owner.transfer(address(this).balance);
}
}
{
"compilationTarget": {
"ETHDenverStaking.sol": "ETHDenverStaking"
},
"evmVersion": "petersburg",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"internalType":"address","name":"_userFortmaticAddress","type":"address"},{"internalType":"uint256","name":"_expiryDate","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stake","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_signer","type":"address"}],"name":"setGrantSigner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"sweepStakes","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userStakedAddress","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address payable","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"finishDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_userFortmaticAddress","type":"address"},{"internalType":"uint256","name":"_expiryDate","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"recoupStake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"grantSigner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_grantSigner","type":"address"},{"internalType":"uint256","name":"_finishDate","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userFortmaticAddress","type":"address"},{"indexed":false,"internalType":"address","name":"walletAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountStaked","type":"uint256"}],"name":"UserStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userFortmaticAddress","type":"address"},{"indexed":false,"internalType":"address","name":"walletAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountStaked","type":"uint256"}],"name":"UserRecoupStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"_msg","type":"bytes32"}],"name":"debugBytes32","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"_msg","type":"bytes"}],"name":"debugBytes","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"_msg","type":"string"}],"name":"debugString","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_address","type":"address"}],"name":"debugAddress","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]