-
Notifications
You must be signed in to change notification settings - Fork 28
/
Shifter.sol
188 lines (156 loc) · 7.84 KB
/
Shifter.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
pragma solidity ^0.5.8;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol";
import "../libraries/String.sol";
import "./ERC20Shifted.sol";
/// @notice Shifter handles verifying mint and burn requests. A mintAuthority
/// approves new assets to be minted by providing a digital signature. An owner
/// of an asset can request for it to be burnt.
contract Shifter is Ownable {
using SafeMath for uint256;
uint8 public version = 2;
uint256 constant BIPS_DENOMINATOR = 10000;
/// @notice Each Shifter token is tied to a specific shifted token.
ERC20Shifted public token;
/// @notice The mintAuthority is an address that can sign mint requests.
address public mintAuthority;
/// @dev feeRecipient is assumed to be an address (or a contract) that can
/// accept erc20 payments it cannot be 0x0.
/// @notice When tokens are mint or burnt, a portion of the tokens are
/// forwarded to a fee recipient.
address public feeRecipient;
/// @notice The minting and burning fee in bips.
uint16 public fee;
/// @notice Each nHash can only be seen once.
mapping (bytes32=>bool) public status;
// LogShiftIn and LogShiftOut contain a unique `shiftID` that identifies
// the mint or burn event.
uint256 public nextShiftID = 0;
event LogShiftIn(address indexed _to, uint256 _amount, uint256 indexed _shiftID);
event LogShiftOut(bytes _to, uint256 _amount, uint256 indexed _shiftID, bytes indexed _indexedTo);
/// @param _token The ERC20Shifted this Shifter is responsible for.
/// @param _feeRecipient The recipient of burning and minting fees.
/// @param _mintAuthority The address of the key that can sign mint
/// requests.
/// @param _fee The amount subtracted each burn and mint request and
/// forwarded to the feeRecipient. In BIPS.
constructor(ERC20Shifted _token, address _feeRecipient, address _mintAuthority, uint16 _fee) public {
token = _token;
mintAuthority = _mintAuthority;
fee = _fee;
updateFeeRecipient(_feeRecipient);
}
// Public functions ////////////////////////////////////////////////////////
/// @notice Claims ownership of the token passed in to the constructor.
/// `transferStoreOwnership` must have previously been called.
/// Anyone can call this function.
function claimTokenOwnership() public {
token.claimOwnership();
}
/// @notice Allow the owner to update the owner of the ERC20Shifted token.
function transferTokenOwnership(Shifter _nextTokenOwner) public onlyOwner {
token.transferOwnership(address(_nextTokenOwner));
_nextTokenOwner.claimTokenOwnership();
}
/// @notice Allow the owner to update the fee recipient.
///
/// @param _nextMintAuthority The address to start paying fees to.
function updateMintAuthority(address _nextMintAuthority) public onlyOwner {
mintAuthority = _nextMintAuthority;
}
/// @notice Allow the owner to update the fee recipient.
///
/// @param _nextFeeRecipient The address to start paying fees to.
function updateFeeRecipient(address _nextFeeRecipient) public onlyOwner {
// ShiftIn and ShiftOut will fail if the feeRecipient is 0x0
require(_nextFeeRecipient != address(0x0), "fee recipient cannot be 0x0");
feeRecipient = _nextFeeRecipient;
}
/// @notice Allow the owner to update the fee.
///
/// @param _nextFee The new fee for minting and burning.
function updateFee(uint16 _nextFee) public onlyOwner {
fee = _nextFee;
}
/// @notice shiftIn mints tokens after taking a fee for the `_feeRecipient`.
///
/// @param _pHash (payload hash) The hash of the payload associated with the
/// shift.
/// @param _amount The amount of the token being shifted int, in its
/// smallest value. (e.g. satoshis for BTC)
/// @param _nHash (nonce hash) The hash of the nonce, amount and pHash.
/// @param _sig The signature of the hash of the following values:
/// (pHash, amount, msg.sender, nHash), signed by the mintAuthority.
function shiftIn(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes memory _sig) public returns (uint256) {
// Verify signature
bytes32 signedMessageHash = hashForSignature(_pHash, _amount, msg.sender, _nHash);
require(status[signedMessageHash] == false, "nonce hash already spent");
if (!verifySignature(signedMessageHash, _sig)) {
// Return a detailed string containing the hash and recovered
// signer. This is a costly operation but is only run in the revert
// branch.
revert(
String.add4(
"invalid signature - hash: ",
String.fromBytes32(signedMessageHash),
", signer: ",
String.fromAddress(ECDSA.recover(signedMessageHash, _sig))
)
);
}
status[signedMessageHash] = true;
// Mint `amount - fee` for the recipient and mint `fee` for the minter
uint256 absoluteFee = (_amount.mul(fee)).div(BIPS_DENOMINATOR);
uint256 receivedAmount = _amount.sub(absoluteFee);
token.mint(msg.sender, receivedAmount);
token.mint(feeRecipient, absoluteFee);
// Emit a log with a unique shift ID
emit LogShiftIn(msg.sender, receivedAmount, nextShiftID);
nextShiftID += 1;
return receivedAmount;
}
/// @notice shiftOut burns tokens after taking a fee for the `_feeRecipient`.
///
/// @param _to The address to receive the unshifted digital asset. The
/// format of this address should be of the destination chain.
/// For example, when shifting out to Bitcoin, _to should be a
/// Bitcoin address.
/// @param _amount The amount of the token being shifted out, in its
/// smallest value. (e.g. satoshis for BTC)
function shiftOut(bytes memory _to, uint256 _amount) public returns (uint256) {
// The recipient must not be empty. Better validation is possible,
// but would need to be customized for each destination ledger.
require(_to.length != 0, "to address is empty");
// Burn full amount and mint fee
uint256 absoluteFee = (_amount.mul(fee)).div(BIPS_DENOMINATOR);
token.burn(msg.sender, _amount);
token.mint(feeRecipient, absoluteFee);
// Emit a log with a unique shift ID
uint256 receivedValue = _amount.sub(absoluteFee);
emit LogShiftOut(_to, receivedValue, nextShiftID, _to);
nextShiftID += 1;
return receivedValue;
}
/// @notice verifySignature checks the the provided signature matches the provided
/// parameters.
function verifySignature(bytes32 _signedMessageHash, bytes memory _sig) public view returns (bool) {
return mintAuthority == ECDSA.recover(_signedMessageHash, _sig);
}
/// @notice hashForSignature hashes the parameters so that they can be signed.
function hashForSignature(bytes32 _pHash, uint256 _amount, address _to, bytes32 _nHash) public view returns (bytes32) {
return keccak256(abi.encode(_pHash, _amount, address(token), _to, _nHash));
}
}
/// @dev The following are not necessary for deploying BTCShifter or ZECShifter
/// contracts, but are used to track deployments.
contract BTCShifter is Shifter {
constructor(ERC20Shifted _token, address _feeRecipient, address _mintAuthority, uint16 _fee)
Shifter(_token, _feeRecipient, _mintAuthority, _fee) public {
}
}
contract ZECShifter is Shifter {
constructor(ERC20Shifted _token, address _feeRecipient, address _mintAuthority, uint16 _fee)
Shifter(_token, _feeRecipient, _mintAuthority, _fee) public {
}
}