-
Notifications
You must be signed in to change notification settings - Fork 17
/
ExchangeV1.sol
238 lines (209 loc) · 9.87 KB
/
ExchangeV1.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../lib/interface/IERC1155.sol";
import "../lib/utils/StringLibrary.sol";
import "../lib/utils/BytesLibrary.sol";
import "../lib/contracts/ERC165.sol";
import "./OwnableOperatorRole.sol";
import "./ERC20TransferProxy.sol";
import "./TransferProxy.sol";
import "./ExchangeOrdersHolderV1.sol";
import "./ExchangeDomainV1.sol";
import "./ExchangeStateV1.sol";
import "./TransferProxyForDeprecated.sol";
import "../lib/contracts/HasSecondarySaleFees.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ExchangeV1 is Ownable, ExchangeDomainV1 {
using SafeMath for uint;
using UintLibrary for uint;
using StringLibrary for string;
using BytesLibrary for bytes32;
enum FeeSide {NONE, SELL, BUY}
event Buy(
address indexed sellToken, uint256 indexed sellTokenId, uint256 sellValue,
address owner,
address buyToken, uint256 buyTokenId, uint256 buyValue,
address buyer,
uint256 amount,
uint256 salt
);
event Cancel(
address indexed sellToken, uint256 indexed sellTokenId,
address owner,
address buyToken, uint256 buyTokenId,
uint256 salt
);
bytes4 private constant _INTERFACE_ID_FEES = 0xb7799584;
uint256 private constant UINT256_MAX = 2 ** 256 - 1;
address payable public beneficiary;
address public buyerFeeSigner;
TransferProxy public transferProxy;
TransferProxyForDeprecated public transferProxyForDeprecated;
ERC20TransferProxy public erc20TransferProxy;
ExchangeStateV1 public state;
ExchangeOrdersHolderV1 public ordersHolder;
constructor(
TransferProxy _transferProxy, TransferProxyForDeprecated _transferProxyForDeprecated, ERC20TransferProxy _erc20TransferProxy, ExchangeStateV1 _state,
ExchangeOrdersHolderV1 _ordersHolder, address payable _beneficiary, address _buyerFeeSigner
) public {
transferProxy = _transferProxy;
transferProxyForDeprecated = _transferProxyForDeprecated;
erc20TransferProxy = _erc20TransferProxy;
state = _state;
ordersHolder = _ordersHolder;
beneficiary = _beneficiary;
buyerFeeSigner = _buyerFeeSigner;
}
function setBeneficiary(address payable newBeneficiary) external onlyOwner {
beneficiary = newBeneficiary;
}
function setBuyerFeeSigner(address newBuyerFeeSigner) external onlyOwner {
buyerFeeSigner = newBuyerFeeSigner;
}
function exchange(
Order calldata order,
Sig calldata sig,
uint buyerFee,
Sig calldata buyerFeeSig,
uint amount,
address buyer
) payable external {
validateOrderSig(order, sig);
validateBuyerFeeSig(order, buyerFee, buyerFeeSig);
uint paying = order.buying.mul(amount).div(order.selling);
verifyOpenAndModifyOrderState(order.key, order.selling, amount);
require(order.key.sellAsset.assetType != AssetType.ETH, "ETH is not supported on sell side");
if (order.key.buyAsset.assetType == AssetType.ETH) {
validateEthTransfer(paying, buyerFee);
}
FeeSide feeSide = getFeeSide(order.key.sellAsset.assetType, order.key.buyAsset.assetType);
if (buyer == address(0x0)) {
buyer = msg.sender;
}
transferWithFeesPossibility(order.key.sellAsset, amount, order.key.owner, buyer, feeSide == FeeSide.SELL, buyerFee, order.sellerFee, order.key.buyAsset);
transferWithFeesPossibility(order.key.buyAsset, paying, msg.sender, order.key.owner, feeSide == FeeSide.BUY, order.sellerFee, buyerFee, order.key.sellAsset);
emitBuy(order, amount, buyer);
}
function validateEthTransfer(uint value, uint buyerFee) internal view {
uint256 buyerFeeValue = value.bp(buyerFee);
require(msg.value == value + buyerFeeValue, "msg.value is incorrect");
}
function cancel(OrderKey calldata key) external {
require(key.owner == msg.sender, "not an owner");
state.setCompleted(key, UINT256_MAX);
emit Cancel(key.sellAsset.token, key.sellAsset.tokenId, msg.sender, key.buyAsset.token, key.buyAsset.tokenId, key.salt);
}
function validateOrderSig(
Order memory order,
Sig memory sig
) internal view {
if (sig.v == 0 && sig.r == bytes32(0x0) && sig.s == bytes32(0x0)) {
require(ordersHolder.exists(order), "incorrect signature");
} else {
require(prepareMessage(order).recover(sig.v, sig.r, sig.s) == order.key.owner, "incorrect signature");
}
}
function validateBuyerFeeSig(
Order memory order,
uint buyerFee,
Sig memory sig
) internal view {
require(prepareBuyerFeeMessage(order, buyerFee).recover(sig.v, sig.r, sig.s) == buyerFeeSigner, "incorrect buyer fee signature");
}
function prepareBuyerFeeMessage(Order memory order, uint fee) public pure returns (string memory) {
return keccak256(abi.encode(order, fee)).toString();
}
function prepareMessage(Order memory order) public pure returns (string memory) {
return keccak256(abi.encode(order)).toString();
}
function transferWithFeesPossibility(Asset memory firstType, uint value, address from, address to, bool hasFee, uint256 sellerFee, uint256 buyerFee, Asset memory secondType) internal {
if (!hasFee) {
transfer(firstType, value, from, to);
} else {
transferWithFees(firstType, value, from, to, sellerFee, buyerFee, secondType);
}
}
function transfer(Asset memory asset, uint value, address from, address to) internal {
if (asset.assetType == AssetType.ETH) {
address payable toPayable = address(uint160(to));
toPayable.transfer(value);
} else if (asset.assetType == AssetType.ERC20) {
require(asset.tokenId == 0, "tokenId should be 0");
erc20TransferProxy.erc20safeTransferFrom(IERC20(asset.token), from, to, value);
} else if (asset.assetType == AssetType.ERC721) {
require(value == 1, "value should be 1 for ERC-721");
transferProxy.erc721safeTransferFrom(IERC721(asset.token), from, to, asset.tokenId);
} else if (asset.assetType == AssetType.ERC721Deprecated) {
require(value == 1, "value should be 1 for ERC-721");
transferProxyForDeprecated.erc721TransferFrom(IERC721(asset.token), from, to, asset.tokenId);
} else {
transferProxy.erc1155safeTransferFrom(IERC1155(asset.token), from, to, asset.tokenId, value, "");
}
}
function transferWithFees(Asset memory firstType, uint value, address from, address to, uint256 sellerFee, uint256 buyerFee, Asset memory secondType) internal {
uint restValue = transferFeeToBeneficiary(firstType, from, value, sellerFee, buyerFee);
if (
secondType.assetType == AssetType.ERC1155 && IERC1155(secondType.token).supportsInterface(_INTERFACE_ID_FEES) ||
(secondType.assetType == AssetType.ERC721 || secondType.assetType == AssetType.ERC721Deprecated) && IERC721(secondType.token).supportsInterface(_INTERFACE_ID_FEES)
) {
HasSecondarySaleFees withFees = HasSecondarySaleFees(secondType.token);
address payable[] memory recipients = withFees.getFeeRecipients(secondType.tokenId);
uint[] memory fees = withFees.getFeeBps(secondType.tokenId);
require(fees.length == recipients.length);
for (uint256 i = 0; i < fees.length; i++) {
(uint newRestValue, uint current) = subFeeInBp(restValue, value, fees[i]);
restValue = newRestValue;
transfer(firstType, current, from, recipients[i]);
}
}
address payable toPayable = address(uint160(to));
transfer(firstType, restValue, from, toPayable);
}
function transferFeeToBeneficiary(Asset memory asset, address from, uint total, uint sellerFee, uint buyerFee) internal returns (uint) {
(uint restValue, uint sellerFeeValue) = subFeeInBp(total, total, sellerFee);
uint buyerFeeValue = total.bp(buyerFee);
uint beneficiaryFee = buyerFeeValue.add(sellerFeeValue);
if (beneficiaryFee > 0) {
transfer(asset, beneficiaryFee, from, beneficiary);
}
return restValue;
}
function emitBuy(Order memory order, uint amount, address buyer) internal {
emit Buy(order.key.sellAsset.token, order.key.sellAsset.tokenId, order.selling,
order.key.owner,
order.key.buyAsset.token, order.key.buyAsset.tokenId, order.buying,
buyer,
amount,
order.key.salt
);
}
function subFeeInBp(uint value, uint total, uint feeInBp) internal pure returns (uint newValue, uint realFee) {
return subFee(value, total.bp(feeInBp));
}
function subFee(uint value, uint fee) internal pure returns (uint newValue, uint realFee) {
if (value > fee) {
newValue = value - fee;
realFee = fee;
} else {
newValue = 0;
realFee = value;
}
}
function verifyOpenAndModifyOrderState(OrderKey memory key, uint selling, uint amount) internal {
uint completed = state.getCompleted(key);
uint newCompleted = completed.add(amount);
require(newCompleted <= selling, "not enough stock of order for buying");
state.setCompleted(key, newCompleted);
}
function getFeeSide(AssetType sellType, AssetType buyType) internal pure returns (FeeSide) {
if ((sellType == AssetType.ERC721 || sellType == AssetType.ERC721Deprecated) &&
(buyType == AssetType.ERC721 || buyType == AssetType.ERC721Deprecated)) {
return FeeSide.NONE;
}
if (uint(sellType) > uint(buyType)) {
return FeeSide.BUY;
}
return FeeSide.SELL;
}
}