-
Notifications
You must be signed in to change notification settings - Fork 16
/
ChannelImplementation.sol
152 lines (116 loc) · 6.5 KB
/
ChannelImplementation.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.9;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { IERC20Token } from "./interfaces/IERC20Token.sol";
import { IHermesContract } from "./interfaces/IHermesContract.sol";
import { IUniswapV2Router } from "./interfaces/IUniswapV2Router.sol";
import { FundsRecovery } from "./FundsRecovery.sol";
import { Utils } from "./Utils.sol";
contract ChannelImplementation is FundsRecovery, Utils {
using ECDSA for bytes32;
string constant EXIT_PREFIX = "Exit request:";
uint256 internal lastNonce;
struct Hermes {
address operator; // signing address
address contractAddress; // hermes smart contract address, funds will be send there
uint256 settled; // total amount already settled by hermes
}
Hermes public hermes;
address public operator; // channel operator = sha3(IdentityPublicKey)[:20]
IUniswapV2Router internal dex; // any uniswap v2 compatible dex router address
event PromiseSettled(address beneficiary, uint256 amount, uint256 totalSettled, bytes32 lock);
event Withdraw(address beneficiary, uint256 amount);
/*
------------------------------------------- SETUP -------------------------------------------
*/
// Fallback function - exchange received ETH into MYST
receive() external payable {
address[] memory path = new address[](2);
path[0] = dex.WETH();
path[1] = address(token);
dex.swapExactETHForTokens{value: msg.value}(0, path, address(this), block.timestamp);
}
// Because of proxy pattern this function is used insted of constructor.
// Have to be called right after proxy deployment.
function initialize(address _token, address _dexAddress, address _identity, address _hermesId, uint256 _fee) public {
require(!isInitialized(), "Is already initialized");
require(_identity != address(0), "Identity can't be zero");
require(_hermesId != address(0), "HermesID can't be zero");
require(_token != address(0), "Token can't be deployd into zero address");
token = IERC20Token(_token);
dex = IUniswapV2Router(_dexAddress);
// Transfer required fee to msg.sender (most probably Registry)
if (_fee > 0) {
token.transfer(msg.sender, _fee);
}
operator = _identity;
transferOwnership(operator);
hermes = Hermes(IHermesContract(_hermesId).getOperator(), _hermesId, 0);
}
function isInitialized() public view returns (bool) {
return operator != address(0);
}
/*
-------------------------------------- MAIN FUNCTIONALITY -----------------------------------
*/
// Settle promise
// signedMessage: channelId, totalSettleAmount, fee, hashlock
// _lock is random number generated by receiver used in HTLC
function settlePromise(uint256 _amount, uint256 _transactorFee, bytes32 _lock, bytes memory _signature) public {
bytes32 _hashlock = keccak256(abi.encode(_lock));
address _channelId = address(this);
address _signer = keccak256(abi.encodePacked(getChainID(), uint256(uint160(_channelId)), _amount, _transactorFee, _hashlock)).recover(_signature);
require(_signer == operator, "have to be signed by channel operator");
// Calculate amount of tokens to be claimed.
uint256 _unpaidAmount = _amount - hermes.settled;
require(_unpaidAmount > 0, "amount to settle should be greater that already settled");
// If signer has less tokens than asked to transfer, we can transfer as much as he has already
// and rest tokens can be transferred via same promise but in another tx
// when signer will top up channel balance.
uint256 _currentBalance = token.balanceOf(_channelId);
if (_unpaidAmount > _currentBalance) {
_unpaidAmount = _currentBalance;
}
// Increase already paid amount
hermes.settled = hermes.settled + _unpaidAmount;
// Send tokens
token.transfer(hermes.contractAddress, _unpaidAmount - _transactorFee);
// Pay fee to transaction maker
if (_transactorFee > 0) {
token.transfer(msg.sender, _transactorFee);
}
emit PromiseSettled(hermes.contractAddress, _unpaidAmount, hermes.settled, _lock);
}
// Fast funds withdrawal is possible when hermes agrees that given amount of funds can be withdrawn
function fastExit(uint256 _amount, uint256 _transactorFee, address _beneficiary, uint256 _validUntil, bytes memory _operatorSignature, bytes memory _hermesSignature) public {
require(_validUntil >= block.timestamp, "Channel: _validUntil have to be greater than or equal to current block timestamp");
address _channelId = address(this);
bytes32 _msgHash = keccak256(abi.encodePacked(EXIT_PREFIX, getChainID(), uint256(uint160(_channelId)), _amount, _transactorFee, uint256(uint160(_beneficiary)), _validUntil, lastNonce++));
address _firstSigner = _msgHash.recover(_operatorSignature);
require(_firstSigner == operator, "Channel: have to be signed by operator");
address _secondSigner = _msgHash.recover(_hermesSignature);
require(_secondSigner == hermes.operator, "Channel: have to be signed by hermes");
// Pay fee to transaction maker
if (_transactorFee > 0) {
require(_amount >= _transactorFee, "Channel: transactor fee can't be bigger that withdrawal amount");
token.transfer(msg.sender, _transactorFee);
}
// Withdraw agreed amount
uint256 _amountToSend = _amount - _transactorFee;
token.transfer(_beneficiary, _amountToSend);
emit Withdraw(_beneficiary, _amountToSend);
}
/*
------------------------------------------ HELPERS ------------------------------------------
*/
// Setting new destination of funds recovery.
string constant FUNDS_DESTINATION_PREFIX = "Set funds destination:";
function setFundsDestinationByCheque(address payable _newDestination, bytes memory _signature) public {
require(_newDestination != address(0));
address _channelId = address(this);
address _signer = keccak256(abi.encodePacked(FUNDS_DESTINATION_PREFIX, _channelId, _newDestination, lastNonce++)).recover(_signature);
require(_signer == operator, "Channel: have to be signed by proper identity");
emit DestinationChanged(fundsDestination, _newDestination);
fundsDestination = _newDestination;
}
}