|
//import "openzeppelin-solidity/contracts/ECRecovery.sol"; |
|
|
|
contract InviteLink { |
|
using ECRecovery for bytes32; |
|
IERC1077 owner; |
|
|
|
// Mappings of transit pub key => true if link is used. |
|
mapping (bytes => bool) usedLinks; |
|
|
|
constructor(IERC1077 _owner) { |
|
owner = owner; |
|
} |
|
|
|
|
|
function transferTokensByLink( |
|
address receiverAddress, |
|
bytes transitPubKey, |
|
address tokenAddress, |
|
uint tokenAmount, |
|
uint gasPrice, |
|
address gasToken, |
|
bytes sigSender, |
|
bytes sigReceiver) public returns (bool) { |
|
|
|
// check that link is valid |
|
require(isLinkValid( |
|
receiverAddress, |
|
transitPubKey, |
|
tokenAddress, |
|
tokenAmount, |
|
gasPrice, |
|
gasToken, |
|
sigSender, |
|
sigReceiver |
|
)); |
|
|
|
// mark link as used, so that it can be used only once |
|
usedLinks[transitPubKey] = true; |
|
|
|
// transfer tokens |
|
_transferTokens(tokenAddress, tokenAmount, receiverAddress); |
|
|
|
return true; |
|
} |
|
|
|
|
|
function isLinkValid( |
|
address receiverAddress, |
|
bytes transitPubKey, |
|
address tokenAddress, |
|
uint tokenAmount, |
|
uint gasPrice, |
|
address gasToken, |
|
bytes sigSender, |
|
bytes sigReceiver) public returns (bool) { |
|
|
|
// 1. check that transitPubKey and transfer params were signed by sender |
|
require(checkSenderSignature( |
|
receiverAddress, |
|
transitPubKey, |
|
tokenAddress, |
|
tokenAmount, |
|
sigSender |
|
)); |
|
|
|
// 2. check that Receiver's address was signed by transit key |
|
require(checkReceiverSignature( |
|
receiverAddress, |
|
transitPubKey, |
|
sigReceiver)); |
|
|
|
// 3. check that link hasn't been used before |
|
require(hasBeenUsed(transitPubKey) == false); |
|
|
|
return true; |
|
} |
|
|
|
|
|
function hasBeenUsed(address transitPubKey) returns (bool) { |
|
return usedLinks[transitPubKey]; |
|
} |
|
|
|
|
|
function checkReceiverSignature( |
|
address receiverAddress, |
|
bytes transitPubKey, |
|
bytes sigReceiver) returns (bool) { |
|
|
|
// hash signed by receiver using transit private key |
|
bytes32 hash = keccak256( |
|
abi.encodePacked( |
|
receiverAddress, |
|
transitPubKey |
|
)); |
|
return hash.toEthSignedMessageHash().recover(signature); |
|
} |
|
|
|
|
|
function checkSenderSignature( |
|
address receiverAddress, |
|
bytes transitPubKey, |
|
address tokenAddress, |
|
uint tokenAmount, |
|
bytes sigSender) internal returns(bool) { |
|
|
|
// calculate hash signed by sender |
|
bytes32 messageHash = keccak256( |
|
abi.encodePacked( |
|
tokenAddress, |
|
tokenAmount, |
|
transitPubKey, |
|
gasPrice, |
|
gasToken |
|
)); |
|
|
|
// check that the hash was signed by sender |
|
return owner.canExecute( |
|
address(this), // to |
|
0, // value (we can set it always to 0) |
|
messageHash, // data |
|
0, // nonce, do we need to have one here? |
|
gasPrice, |
|
gasToken, |
|
100000, // gasLimit, can be hardcoded as we have the same use case for these transactions - transfer by link |
|
4, // OperationType, should we introduce special operationType for this purpose? |
|
sigSender); |
|
} |
|
|
|
|
|
function _transferTokens( |
|
address receiverAddress, |
|
address tokenAddress, |
|
uint tokenAmount) internal returns (bool) { |
|
|
|
bytes data = abi.encodePacked( |
|
0xa9059cbb000000000000000000000000, // transfer method hardcoded |
|
tokenAmount, |
|
receiverAddress |
|
); |
|
|
|
// execute function send token |
|
return owner.moduleExecute( |
|
tokenAddress, |
|
0, |
|
data |
|
); |
|
} |
|
|
|
|
|
// links module never executes |
|
function canExecute( |
|
address to, |
|
uint256 value, |
|
bytes data, |
|
uint nonce, |
|
uint gasPrice, |
|
address gasToken, |
|
uint gasLimit, |
|
IERC1077.OperationType operationType, |
|
bytes signatures) public view returns (bool) { |
|
//Ignore |
|
return false; |
|
} |
|
} |
|
} |