文件 1 的 12:Address.sol
pragma solidity ^0.6.8;
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 2 的 12:AssetRegistry.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
import { Address } from './Address.sol';
import { IERC20, Structs } from './Interfaces.sol';
library AssetRegistry {
struct Storage {
mapping(address => Structs.Asset) assetsByAddress;
mapping(string => Structs.Asset[]) assetsBySymbol;
}
function registerToken(
Storage storage self,
IERC20 tokenAddress,
string memory symbol,
uint8 decimals
) internal {
require(decimals <= 32, 'Token cannot have more than 32 decimals');
require(
tokenAddress != IERC20(0x0) && Address.isContract(address(tokenAddress)),
'Invalid token address'
);
require(bytes(symbol).length > 0, 'Invalid token symbol');
require(
!self.assetsByAddress[address(tokenAddress)].isConfirmed,
'Token already finalized'
);
self.assetsByAddress[address(tokenAddress)] = Structs.Asset({
exists: true,
assetAddress: address(tokenAddress),
symbol: symbol,
decimals: decimals,
isConfirmed: false,
confirmedTimestampInMs: 0
});
}
function confirmTokenRegistration(
Storage storage self,
IERC20 tokenAddress,
string memory symbol,
uint8 decimals
) internal {
Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)];
require(asset.exists, 'Unknown token');
require(!asset.isConfirmed, 'Token already finalized');
require(isStringEqual(asset.symbol, symbol), 'Symbols do not match');
require(asset.decimals == decimals, 'Decimals do not match');
asset.isConfirmed = true;
asset.confirmedTimestampInMs = uint64(block.timestamp * 1000);
self.assetsByAddress[address(tokenAddress)] = asset;
self.assetsBySymbol[symbol].push(asset);
}
function addTokenSymbol(
Storage storage self,
IERC20 tokenAddress,
string memory symbol
) internal {
Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)];
require(
asset.exists && asset.isConfirmed,
'Registration of token not finalized'
);
require(!isStringEqual(symbol, 'ETH'), 'ETH symbol reserved for Ether');
uint64 msInOneSecond = 1000;
asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond);
self.assetsBySymbol[symbol].push(asset);
}
function loadAssetByAddress(Storage storage self, address assetAddress)
internal
view
returns (Structs.Asset memory)
{
if (assetAddress == address(0x0)) {
return getEthAsset();
}
Structs.Asset memory asset = self.assetsByAddress[assetAddress];
require(
asset.exists && asset.isConfirmed,
'No confirmed asset found for address'
);
return asset;
}
function loadAssetBySymbol(
Storage storage self,
string memory symbol,
uint64 timestampInMs
) internal view returns (Structs.Asset memory) {
if (isStringEqual('ETH', symbol)) {
return getEthAsset();
}
Structs.Asset memory asset;
if (self.assetsBySymbol[symbol].length > 0) {
for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) {
if (
self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs
) {
asset = self.assetsBySymbol[symbol][i];
}
}
}
require(
asset.exists && asset.isConfirmed,
'No confirmed asset found for symbol'
);
return asset;
}
function getEthAsset() private pure returns (Structs.Asset memory) {
return Structs.Asset(true, address(0x0), 'ETH', 18, true, 0);
}
function isStringEqual(string memory a, string memory b)
private
pure
returns (bool)
{
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}
文件 3 的 12:AssetTransfers.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
import {
SafeMath as SafeMath256
} from './SafeMath.sol';
import { IERC20 } from './Interfaces.sol';
library AssetTransfers {
using SafeMath256 for uint256;
function transferFrom(
address wallet,
IERC20 tokenAddress,
uint256 quantityInAssetUnits
) internal {
uint256 balanceBefore = tokenAddress.balanceOf(address(this));
tokenAddress.transferFrom(wallet, address(this), quantityInAssetUnits);
uint256 balanceAfter = tokenAddress.balanceOf(address(this));
require(
balanceAfter.sub(balanceBefore) == quantityInAssetUnits,
'Token contract returned transferFrom success without expected balance change'
);
}
function transferTo(
address payable walletOrContract,
address asset,
uint256 quantityInAssetUnits
) internal {
if (asset == address(0x0)) {
require(
walletOrContract.send(quantityInAssetUnits),
'ETH transfer failed'
);
} else {
uint256 balanceBefore = IERC20(asset).balanceOf(walletOrContract);
IERC20(asset).transfer(walletOrContract, quantityInAssetUnits);
uint256 balanceAfter = IERC20(asset).balanceOf(walletOrContract);
require(
balanceAfter.sub(balanceBefore) == quantityInAssetUnits,
'Token contract returned transfer success without expected balance change'
);
}
}
}
文件 4 的 12:AssetUnitConversions.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
import {
SafeMath as SafeMath256
} from './SafeMath.sol';
library AssetUnitConversions {
using SafeMath256 for uint256;
function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals)
internal
pure
returns (uint256)
{
require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');
if (assetDecimals > 8) {
return uint256(quantityInPips).mul(uint256(10)**(assetDecimals - 8));
}
return uint256(quantityInPips).div(uint256(10)**(8 - assetDecimals));
}
function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals)
internal
pure
returns (uint64)
{
require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');
uint256 quantityInPips;
if (assetDecimals > 8) {
quantityInPips = quantityInAssetUnits.div(
uint256(10)**(assetDecimals - 8)
);
} else {
quantityInPips = quantityInAssetUnits.mul(
uint256(10)**(8 - assetDecimals)
);
}
require(quantityInPips < 2**64, 'Pip quantity overflows uint64');
return uint64(quantityInPips);
}
}
文件 5 的 12:ECDSA.sol
pragma solidity ^0.6.8;
library ECDSA {
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
revert("ECDSA: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("ECDSA: invalid signature 'v' value");
}
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
文件 6 的 12:Exchange.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
import { Address } from './Address.sol';
import { ECDSA } from './ECDSA.sol';
import {
SafeMath as SafeMath256
} from './SafeMath.sol';
import { AssetRegistry } from './AssetRegistry.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Owned } from './Owned.sol';
import { SafeMath64 } from './SafeMath64.sol';
import { Signatures } from './Signatures.sol';
import {
Enums,
ICustodian,
IERC20,
IExchange,
Structs
} from './Interfaces.sol';
import { UUID } from './UUID.sol';
contract Exchange is IExchange, Owned {
using SafeMath64 for uint64;
using SafeMath256 for uint256;
using AssetRegistry for AssetRegistry.Storage;
event ChainPropagationPeriodChanged(uint256 previousValue, uint256 newValue);
event Deposited(
uint64 index,
address indexed wallet,
address indexed assetAddress,
string indexed assetSymbolIndex,
string assetSymbol,
uint64 quantityInPips,
uint64 newExchangeBalanceInPips,
uint256 newExchangeBalanceInAssetUnits
);
event DispatcherChanged(address previousValue, address newValue);
event FeeWalletChanged(address previousValue, address newValue);
event OrderNonceInvalidated(
address indexed wallet,
uint128 nonce,
uint128 timestampInMs,
uint256 effectiveBlockNumber
);
event TokenRegistered(
IERC20 indexed assetAddress,
string assetSymbol,
uint8 decimals
);
event TokenRegistrationConfirmed(
IERC20 indexed assetAddress,
string assetSymbol,
uint8 decimals
);
event TokenSymbolAdded(IERC20 indexed assetAddress, string assetSymbol);
event TradeExecuted(
address buyWallet,
address sellWallet,
string indexed baseAssetSymbolIndex,
string indexed quoteAssetSymbolIndex,
string baseAssetSymbol,
string quoteAssetSymbol,
uint64 baseQuantityInPips,
uint64 quoteQuantityInPips,
uint64 tradePriceInPips,
bytes32 buyOrderHash,
bytes32 sellOrderHash
);
event WalletExited(address indexed wallet, uint256 effectiveBlockNumber);
event WalletExitWithdrawn(
address indexed wallet,
address indexed assetAddress,
string assetSymbol,
uint64 quantityInPips,
uint64 newExchangeBalanceInPips,
uint256 newExchangeBalanceInAssetUnits
);
event WalletExitCleared(address indexed wallet);
event Withdrawn(
address indexed wallet,
address indexed assetAddress,
string assetSymbol,
uint64 quantityInPips,
uint64 newExchangeBalanceInPips,
uint256 newExchangeBalanceInAssetUnits
);
struct NonceInvalidation {
bool exists;
uint64 timestampInMs;
uint256 effectiveBlockNumber;
}
struct WalletExit {
bool exists;
uint256 effectiveBlockNumber;
}
AssetRegistry.Storage _assetRegistry;
mapping(bytes32 => bool) _completedOrderHashes;
mapping(bytes32 => bool) _completedWithdrawalHashes;
address payable _custodian;
uint64 _depositIndex;
mapping(address => mapping(address => uint64)) _balancesInPips;
mapping(address => NonceInvalidation) _nonceInvalidations;
mapping(bytes32 => uint64) _partiallyFilledOrderQuantitiesInPips;
mapping(address => WalletExit) _walletExits;
uint256 _chainPropagationPeriod;
address _dispatcherWallet;
address _feeWallet;
uint256 constant _maxChainPropagationPeriod = (7 * 24 * 60 * 60) / 15;
uint64 constant _maxTradeFeeBasisPoints = 20 * 100;
uint64 constant _maxWithdrawalFeeBasisPoints = 20 * 100;
constructor() public Owned() {}
function setCustodian(address payable newCustodian) external onlyAdmin {
require(_custodian == address(0x0), 'Custodian can only be set once');
require(Address.isContract(newCustodian), 'Invalid address');
_custodian = newCustodian;
}
function setChainPropagationPeriod(uint256 newChainPropagationPeriod)
external
onlyAdmin
{
require(
newChainPropagationPeriod < _maxChainPropagationPeriod,
'Must be less than 1 week'
);
uint256 oldChainPropagationPeriod = _chainPropagationPeriod;
_chainPropagationPeriod = newChainPropagationPeriod;
emit ChainPropagationPeriodChanged(
oldChainPropagationPeriod,
newChainPropagationPeriod
);
}
function setFeeWallet(address newFeeWallet) external onlyAdmin {
require(newFeeWallet != address(0x0), 'Invalid wallet address');
require(
newFeeWallet != _feeWallet,
'Must be different from current fee wallet'
);
address oldFeeWallet = _feeWallet;
_feeWallet = newFeeWallet;
emit FeeWalletChanged(oldFeeWallet, newFeeWallet);
}
function loadBalanceInAssetUnitsByAddress(
address wallet,
address assetAddress
) external view returns (uint256) {
require(wallet != address(0x0), 'Invalid wallet address');
Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
assetAddress
);
return
AssetUnitConversions.pipsToAssetUnits(
_balancesInPips[wallet][assetAddress],
asset.decimals
);
}
function loadBalanceInAssetUnitsBySymbol(
address wallet,
string calldata assetSymbol
) external view returns (uint256) {
require(wallet != address(0x0), 'Invalid wallet address');
Structs.Asset memory asset = _assetRegistry.loadAssetBySymbol(
assetSymbol,
getCurrentTimestampInMs()
);
return
AssetUnitConversions.pipsToAssetUnits(
_balancesInPips[wallet][asset.assetAddress],
asset.decimals
);
}
function loadBalanceInPipsByAddress(address wallet, address assetAddress)
external
view
returns (uint64)
{
require(wallet != address(0x0), 'Invalid wallet address');
return _balancesInPips[wallet][assetAddress];
}
function loadBalanceInPipsBySymbol(
address wallet,
string calldata assetSymbol
) external view returns (uint64) {
require(wallet != address(0x0), 'Invalid wallet address');
address assetAddress = _assetRegistry
.loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs())
.assetAddress;
return _balancesInPips[wallet][assetAddress];
}
function loadFeeWallet() external view returns (address) {
return _feeWallet;
}
function loadPartiallyFilledOrderQuantityInPips(bytes32 orderHash)
external
view
returns (uint64)
{
return _partiallyFilledOrderQuantitiesInPips[orderHash];
}
function depositEther() external payable {
deposit(msg.sender, address(0x0), msg.value);
}
function depositTokenByAddress(
IERC20 tokenAddress,
uint256 quantityInAssetUnits
) external {
require(
address(tokenAddress) != address(0x0),
'Use depositEther to deposit Ether'
);
deposit(msg.sender, address(tokenAddress), quantityInAssetUnits);
}
function depositTokenBySymbol(
string calldata assetSymbol,
uint256 quantityInAssetUnits
) external {
IERC20 tokenAddress = IERC20(
_assetRegistry
.loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs())
.assetAddress
);
require(
address(tokenAddress) != address(0x0),
'Use depositEther to deposit ETH'
);
deposit(msg.sender, address(tokenAddress), quantityInAssetUnits);
}
function deposit(
address payable wallet,
address assetAddress,
uint256 quantityInAssetUnits
) private {
require(!_walletExits[wallet].exists, 'Wallet exited');
Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
assetAddress
);
uint64 quantityInPips = AssetUnitConversions.assetUnitsToPips(
quantityInAssetUnits,
asset.decimals
);
require(quantityInPips > 0, 'Quantity is too low');
uint256 quantityInAssetUnitsWithoutFractionalPips = AssetUnitConversions
.pipsToAssetUnits(quantityInPips, asset.decimals);
if (assetAddress != address(0x0)) {
AssetTransfers.transferFrom(
wallet,
IERC20(assetAddress),
quantityInAssetUnitsWithoutFractionalPips
);
}
AssetTransfers.transferTo(
_custodian,
assetAddress,
quantityInAssetUnitsWithoutFractionalPips
);
uint64 newExchangeBalanceInPips = _balancesInPips[wallet][assetAddress].add(
quantityInPips
);
uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions
.pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals);
_balancesInPips[wallet][assetAddress] = newExchangeBalanceInPips;
_depositIndex++;
emit Deposited(
_depositIndex,
wallet,
assetAddress,
asset.symbol,
asset.symbol,
quantityInPips,
newExchangeBalanceInPips,
newExchangeBalanceInAssetUnits
);
}
function invalidateOrderNonce(uint128 nonce) external {
uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce);
require(
timestampInMs < getOneDayFromNowInMs(),
'Nonce timestamp too far in future'
);
if (_nonceInvalidations[msg.sender].exists) {
require(
_nonceInvalidations[msg.sender].timestampInMs < timestampInMs,
'Nonce timestamp already invalidated'
);
require(
_nonceInvalidations[msg.sender].effectiveBlockNumber <= block.number,
'Previous invalidation awaiting chain propagation'
);
}
uint256 effectiveBlockNumber = block.number + _chainPropagationPeriod;
_nonceInvalidations[msg.sender] = NonceInvalidation(
true,
timestampInMs,
effectiveBlockNumber
);
emit OrderNonceInvalidated(
msg.sender,
nonce,
timestampInMs,
effectiveBlockNumber
);
}
function withdraw(Structs.Withdrawal memory withdrawal)
public
override
onlyDispatcher
{
require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited');
require(
getFeeBasisPoints(withdrawal.gasFeeInPips, withdrawal.quantityInPips) <=
_maxWithdrawalFeeBasisPoints,
'Excessive withdrawal fee'
);
bytes32 withdrawalHash = validateWithdrawalSignature(withdrawal);
require(
!_completedWithdrawalHashes[withdrawalHash],
'Hash already withdrawn'
);
Structs.Asset memory asset = withdrawal.withdrawalType ==
Enums.WithdrawalType.BySymbol
? _assetRegistry.loadAssetBySymbol(
withdrawal.assetSymbol,
UUID.getTimestampInMsFromUuidV1(withdrawal.nonce)
)
: _assetRegistry.loadAssetByAddress(withdrawal.assetAddress);
uint64 netAssetQuantityInPips = withdrawal.quantityInPips.sub(
withdrawal.gasFeeInPips
);
uint256 netAssetQuantityInAssetUnits = AssetUnitConversions
.pipsToAssetUnits(netAssetQuantityInPips, asset.decimals);
uint64 newExchangeBalanceInPips = _balancesInPips[withdrawal
.walletAddress][asset.assetAddress]
.sub(withdrawal.quantityInPips);
uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions
.pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals);
_balancesInPips[withdrawal.walletAddress][asset
.assetAddress] = newExchangeBalanceInPips;
_balancesInPips[_feeWallet][asset
.assetAddress] = _balancesInPips[_feeWallet][asset.assetAddress].add(
withdrawal.gasFeeInPips
);
ICustodian(_custodian).withdraw(
withdrawal.walletAddress,
asset.assetAddress,
netAssetQuantityInAssetUnits
);
_completedWithdrawalHashes[withdrawalHash] = true;
emit Withdrawn(
withdrawal.walletAddress,
asset.assetAddress,
asset.symbol,
withdrawal.quantityInPips,
newExchangeBalanceInPips,
newExchangeBalanceInAssetUnits
);
}
function exitWallet() external {
require(!_walletExits[msg.sender].exists, 'Wallet already exited');
_walletExits[msg.sender] = WalletExit(
true,
block.number + _chainPropagationPeriod
);
emit WalletExited(msg.sender, block.number + _chainPropagationPeriod);
}
function withdrawExit(address assetAddress) external {
require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized');
Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
assetAddress
);
uint64 balanceInPips = _balancesInPips[msg.sender][assetAddress];
uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
balanceInPips,
asset.decimals
);
require(balanceInAssetUnits > 0, 'No balance for asset');
_balancesInPips[msg.sender][assetAddress] = 0;
ICustodian(_custodian).withdraw(
msg.sender,
assetAddress,
balanceInAssetUnits
);
emit WalletExitWithdrawn(
msg.sender,
assetAddress,
asset.symbol,
balanceInPips,
0,
0
);
}
function clearWalletExit() external {
require(_walletExits[msg.sender].exists, 'Wallet not exited');
delete _walletExits[msg.sender];
emit WalletExitCleared(msg.sender);
}
function isWalletExitFinalized(address wallet) internal view returns (bool) {
WalletExit storage exit = _walletExits[wallet];
return exit.exists && exit.effectiveBlockNumber <= block.number;
}
function executeTrade(
Structs.Order memory buy,
Structs.Order memory sell,
Structs.Trade memory trade
) public override onlyDispatcher {
require(
!isWalletExitFinalized(buy.walletAddress),
'Buy wallet exit finalized'
);
require(
!isWalletExitFinalized(sell.walletAddress),
'Sell wallet exit finalized'
);
require(
buy.walletAddress != sell.walletAddress,
'Self-trading not allowed'
);
validateAssetPair(buy, sell, trade);
validateLimitPrices(buy, sell, trade);
validateOrderNonces(buy, sell);
(bytes32 buyHash, bytes32 sellHash) = validateOrderSignatures(
buy,
sell,
trade
);
validateTradeFees(trade);
updateOrderFilledQuantities(buy, buyHash, sell, sellHash, trade);
updateBalancesForTrade(buy, sell, trade);
emit TradeExecuted(
buy.walletAddress,
sell.walletAddress,
trade.baseAssetSymbol,
trade.quoteAssetSymbol,
trade.baseAssetSymbol,
trade.quoteAssetSymbol,
trade.grossBaseQuantityInPips,
trade.grossQuoteQuantityInPips,
trade.priceInPips,
buyHash,
sellHash
);
}
function updateBalancesForTrade(
Structs.Order memory buy,
Structs.Order memory sell,
Structs.Trade memory trade
) private {
_balancesInPips[sell.walletAddress][trade
.baseAssetAddress] = _balancesInPips[sell.walletAddress][trade
.baseAssetAddress]
.sub(trade.grossBaseQuantityInPips);
_balancesInPips[buy.walletAddress][trade
.baseAssetAddress] = _balancesInPips[buy.walletAddress][trade
.baseAssetAddress]
.add(trade.netBaseQuantityInPips);
_balancesInPips[buy.walletAddress][trade
.quoteAssetAddress] = _balancesInPips[buy.walletAddress][trade
.quoteAssetAddress]
.sub(trade.grossQuoteQuantityInPips);
_balancesInPips[sell.walletAddress][trade
.quoteAssetAddress] = _balancesInPips[sell.walletAddress][trade
.quoteAssetAddress]
.add(trade.netQuoteQuantityInPips);
_balancesInPips[_feeWallet][trade
.makerFeeAssetAddress] = _balancesInPips[_feeWallet][trade
.makerFeeAssetAddress]
.add(trade.makerFeeQuantityInPips);
_balancesInPips[_feeWallet][trade
.takerFeeAssetAddress] = _balancesInPips[_feeWallet][trade
.takerFeeAssetAddress]
.add(trade.takerFeeQuantityInPips);
}
function updateOrderFilledQuantities(
Structs.Order memory buyOrder,
bytes32 buyOrderHash,
Structs.Order memory sellOrder,
bytes32 sellOrderHash,
Structs.Trade memory trade
) private {
updateOrderFilledQuantity(buyOrder, buyOrderHash, trade);
updateOrderFilledQuantity(sellOrder, sellOrderHash, trade);
}
function updateOrderFilledQuantity(
Structs.Order memory order,
bytes32 orderHash,
Structs.Trade memory trade
) private {
require(!_completedOrderHashes[orderHash], 'Order double filled');
uint64 newFilledQuantityInPips;
if (order.isQuantityInQuote) {
require(
isMarketOrderType(order.orderType),
'Order quote quantity only valid for market orders'
);
newFilledQuantityInPips = trade.grossQuoteQuantityInPips.add(
_partiallyFilledOrderQuantitiesInPips[orderHash]
);
} else {
newFilledQuantityInPips = trade.grossBaseQuantityInPips.add(
_partiallyFilledOrderQuantitiesInPips[orderHash]
);
}
require(
newFilledQuantityInPips <= order.quantityInPips,
'Order overfilled'
);
if (newFilledQuantityInPips < order.quantityInPips) {
_partiallyFilledOrderQuantitiesInPips[orderHash] = newFilledQuantityInPips;
} else {
delete _partiallyFilledOrderQuantitiesInPips[orderHash];
_completedOrderHashes[orderHash] = true;
}
}
function validateAssetPair(
Structs.Order memory buy,
Structs.Order memory sell,
Structs.Trade memory trade
) private view {
require(
trade.baseAssetAddress != trade.quoteAssetAddress,
'Base and quote assets must be different'
);
Structs.Asset memory buyBaseAsset = _assetRegistry.loadAssetBySymbol(
trade.baseAssetSymbol,
UUID.getTimestampInMsFromUuidV1(buy.nonce)
);
Structs.Asset memory buyQuoteAsset = _assetRegistry.loadAssetBySymbol(
trade.quoteAssetSymbol,
UUID.getTimestampInMsFromUuidV1(buy.nonce)
);
require(
buyBaseAsset.assetAddress == trade.baseAssetAddress &&
buyQuoteAsset.assetAddress == trade.quoteAssetAddress,
'Buy order market symbol address resolution mismatch'
);
Structs.Asset memory sellBaseAsset = _assetRegistry.loadAssetBySymbol(
trade.baseAssetSymbol,
UUID.getTimestampInMsFromUuidV1(sell.nonce)
);
Structs.Asset memory sellQuoteAsset = _assetRegistry.loadAssetBySymbol(
trade.quoteAssetSymbol,
UUID.getTimestampInMsFromUuidV1(sell.nonce)
);
require(
sellBaseAsset.assetAddress == trade.baseAssetAddress &&
sellQuoteAsset.assetAddress == trade.quoteAssetAddress,
'Sell order market symbol address resolution mismatch'
);
require(
trade.makerFeeAssetAddress == trade.baseAssetAddress ||
trade.makerFeeAssetAddress == trade.quoteAssetAddress,
'Maker fee asset is not in trade pair'
);
require(
trade.takerFeeAssetAddress == trade.baseAssetAddress ||
trade.takerFeeAssetAddress == trade.quoteAssetAddress,
'Taker fee asset is not in trade pair'
);
require(
trade.makerFeeAssetAddress != trade.takerFeeAssetAddress,
'Maker and taker fee assets must be different'
);
}
function validateLimitPrices(
Structs.Order memory buy,
Structs.Order memory sell,
Structs.Trade memory trade
) private pure {
require(
trade.grossBaseQuantityInPips > 0,
'Base quantity must be greater than zero'
);
require(
trade.grossQuoteQuantityInPips > 0,
'Quote quantity must be greater than zero'
);
if (isLimitOrderType(buy.orderType)) {
require(
getImpliedQuoteQuantityInPips(
trade.grossBaseQuantityInPips,
buy.limitPriceInPips
) >= trade.grossQuoteQuantityInPips,
'Buy order limit price exceeded'
);
}
if (isLimitOrderType(sell.orderType)) {
require(
getImpliedQuoteQuantityInPips(
trade.grossBaseQuantityInPips,
sell.limitPriceInPips
) <= trade.grossQuoteQuantityInPips,
'Sell order limit price exceeded'
);
}
}
function validateTradeFees(Structs.Trade memory trade) private pure {
uint64 makerTotalQuantityInPips = trade.makerFeeAssetAddress ==
trade.baseAssetAddress
? trade.grossBaseQuantityInPips
: trade.grossQuoteQuantityInPips;
require(
getFeeBasisPoints(
trade.makerFeeQuantityInPips,
makerTotalQuantityInPips
) <= _maxTradeFeeBasisPoints,
'Excessive maker fee'
);
uint64 takerTotalQuantityInPips = trade.takerFeeAssetAddress ==
trade.baseAssetAddress
? trade.grossBaseQuantityInPips
: trade.grossQuoteQuantityInPips;
require(
getFeeBasisPoints(
trade.takerFeeQuantityInPips,
takerTotalQuantityInPips
) <= _maxTradeFeeBasisPoints,
'Excessive taker fee'
);
require(
trade.netBaseQuantityInPips.add(
trade.makerFeeAssetAddress == trade.baseAssetAddress
? trade.makerFeeQuantityInPips
: trade.takerFeeQuantityInPips
) == trade.grossBaseQuantityInPips,
'Net base plus fee is not equal to gross'
);
require(
trade.netQuoteQuantityInPips.add(
trade.makerFeeAssetAddress == trade.quoteAssetAddress
? trade.makerFeeQuantityInPips
: trade.takerFeeQuantityInPips
) == trade.grossQuoteQuantityInPips,
'Net quote plus fee is not equal to gross'
);
}
function validateOrderSignatures(
Structs.Order memory buy,
Structs.Order memory sell,
Structs.Trade memory trade
) private pure returns (bytes32, bytes32) {
bytes32 buyOrderHash = validateOrderSignature(buy, trade);
bytes32 sellOrderHash = validateOrderSignature(sell, trade);
return (buyOrderHash, sellOrderHash);
}
function validateOrderSignature(
Structs.Order memory order,
Structs.Trade memory trade
) private pure returns (bytes32) {
bytes32 orderHash = Signatures.getOrderWalletHash(
order,
trade.baseAssetSymbol,
trade.quoteAssetSymbol
);
require(
Signatures.isSignatureValid(
orderHash,
order.walletSignature,
order.walletAddress
),
order.side == Enums.OrderSide.Buy
? 'Invalid wallet signature for buy order'
: 'Invalid wallet signature for sell order'
);
return orderHash;
}
function validateOrderNonces(
Structs.Order memory buy,
Structs.Order memory sell
) private view {
require(
UUID.getTimestampInMsFromUuidV1(buy.nonce) >
getLastInvalidatedTimestamp(buy.walletAddress),
'Buy order nonce timestamp too low'
);
require(
UUID.getTimestampInMsFromUuidV1(sell.nonce) >
getLastInvalidatedTimestamp(sell.walletAddress),
'Sell order nonce timestamp too low'
);
}
function validateWithdrawalSignature(Structs.Withdrawal memory withdrawal)
private
pure
returns (bytes32)
{
bytes32 withdrawalHash = Signatures.getWithdrawalWalletHash(withdrawal);
require(
Signatures.isSignatureValid(
withdrawalHash,
withdrawal.walletSignature,
withdrawal.walletAddress
),
'Invalid wallet signature'
);
return withdrawalHash;
}
function registerToken(
IERC20 tokenAddress,
string calldata symbol,
uint8 decimals
) external onlyAdmin {
_assetRegistry.registerToken(tokenAddress, symbol, decimals);
emit TokenRegistered(tokenAddress, symbol, decimals);
}
function confirmTokenRegistration(
IERC20 tokenAddress,
string calldata symbol,
uint8 decimals
) external onlyAdmin {
_assetRegistry.confirmTokenRegistration(tokenAddress, symbol, decimals);
emit TokenRegistrationConfirmed(tokenAddress, symbol, decimals);
}
function addTokenSymbol(IERC20 tokenAddress, string calldata symbol)
external
onlyAdmin
{
_assetRegistry.addTokenSymbol(tokenAddress, symbol);
emit TokenSymbolAdded(tokenAddress, symbol);
}
function loadAssetBySymbol(string calldata assetSymbol, uint64 timestampInMs)
external
view
returns (Structs.Asset memory)
{
return _assetRegistry.loadAssetBySymbol(assetSymbol, timestampInMs);
}
function setDispatcher(address newDispatcherWallet) external onlyAdmin {
require(newDispatcherWallet != address(0x0), 'Invalid wallet address');
require(
newDispatcherWallet != _dispatcherWallet,
'Must be different from current dispatcher'
);
address oldDispatcherWallet = _dispatcherWallet;
_dispatcherWallet = newDispatcherWallet;
emit DispatcherChanged(oldDispatcherWallet, newDispatcherWallet);
}
function removeDispatcher() external onlyAdmin {
emit DispatcherChanged(_dispatcherWallet, address(0x0));
_dispatcherWallet = address(0x0);
}
modifier onlyDispatcher() {
require(msg.sender == _dispatcherWallet, 'Caller is not dispatcher');
_;
}
function isLimitOrderType(Enums.OrderType orderType)
private
pure
returns (bool)
{
return
orderType == Enums.OrderType.Limit ||
orderType == Enums.OrderType.LimitMaker ||
orderType == Enums.OrderType.StopLossLimit ||
orderType == Enums.OrderType.TakeProfitLimit;
}
function isMarketOrderType(Enums.OrderType orderType)
private
pure
returns (bool)
{
return
orderType == Enums.OrderType.Market ||
orderType == Enums.OrderType.StopLoss ||
orderType == Enums.OrderType.TakeProfit;
}
function getCurrentTimestampInMs() private view returns (uint64) {
uint64 msInOneSecond = 1000;
return uint64(block.timestamp) * msInOneSecond;
}
function getFeeBasisPoints(uint64 fee, uint64 total)
private
pure
returns (uint64)
{
uint64 basisPointsInTotal = 100 * 100;
return fee.mul(basisPointsInTotal).div(total);
}
function getImpliedQuoteQuantityInPips(
uint64 baseQuantityInPips,
uint64 limitPriceInPips
) private pure returns (uint64) {
uint256 pipsMultiplier = 10**8;
uint256 impliedQuoteQuantityInPips = uint256(baseQuantityInPips)
.mul(uint256(limitPriceInPips))
.div(pipsMultiplier);
require(
impliedQuoteQuantityInPips < 2**64,
'Implied quote pip quantity overflows uint64'
);
return uint64(impliedQuoteQuantityInPips);
}
function getLastInvalidatedTimestamp(address walletAddress)
private
view
returns (uint64)
{
if (
_nonceInvalidations[walletAddress].exists &&
_nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number
) {
return _nonceInvalidations[walletAddress].timestampInMs;
}
return 0;
}
function getOneDayFromNowInMs() private view returns (uint64) {
uint64 secondsInOneDay = 24 * 60 * 60;
uint64 msInOneSecond = 1000;
return (uint64(block.timestamp) + secondsInOneDay) * msInOneSecond;
}
}
文件 7 的 12:Interfaces.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
contract Enums {
enum OrderSelfTradePrevention {
dc,
co,
cn,
cb
}
enum OrderSide { Buy, Sell }
enum OrderTimeInForce {
gtc,
gtt,
ioc,
fok
}
enum OrderType {
Market,
Limit,
LimitMaker,
StopLoss,
StopLossLimit,
TakeProfit,
TakeProfitLimit
}
enum WithdrawalType { BySymbol, ByAddress }
}
contract Structs {
struct Order {
uint8 signatureHashVersion;
uint128 nonce;
address walletAddress;
Enums.OrderType orderType;
Enums.OrderSide side;
uint64 quantityInPips;
bool isQuantityInQuote;
uint64 limitPriceInPips;
uint64 stopPriceInPips;
string clientOrderId;
Enums.OrderTimeInForce timeInForce;
Enums.OrderSelfTradePrevention selfTradePrevention;
uint64 cancelAfter;
bytes walletSignature;
}
struct Asset {
bool exists;
address assetAddress;
string symbol;
uint8 decimals;
bool isConfirmed;
uint64 confirmedTimestampInMs;
}
struct Trade {
string baseAssetSymbol;
string quoteAssetSymbol;
address baseAssetAddress;
address quoteAssetAddress;
uint64 grossBaseQuantityInPips;
uint64 grossQuoteQuantityInPips;
uint64 netBaseQuantityInPips;
uint64 netQuoteQuantityInPips;
address makerFeeAssetAddress;
address takerFeeAssetAddress;
uint64 makerFeeQuantityInPips;
uint64 takerFeeQuantityInPips;
uint64 priceInPips;
Enums.OrderSide makerSide;
}
struct Withdrawal {
Enums.WithdrawalType withdrawalType;
uint128 nonce;
address payable walletAddress;
string assetSymbol;
address assetAddress;
uint64 quantityInPips;
uint64 gasFeeInPips;
bool autoDispatchEnabled;
bytes walletSignature;
}
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external;
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface ICustodian {
receive() external payable;
function withdraw(
address payable wallet,
address asset,
uint256 quantityInAssetUnits
) external;
function loadExchange() external view returns (address);
function setExchange(address newExchange) external;
function loadGovernance() external view returns (address);
function setGovernance(address newGovernance) external;
}
interface IExchange {
function executeTrade(
Structs.Order calldata buy,
Structs.Order calldata sell,
Structs.Trade calldata trade
) external;
function withdraw(Structs.Withdrawal calldata withdrawal) external;
}
文件 8 的 12:Owned.sol
pragma solidity 0.6.8;
abstract contract Owned {
address immutable _owner;
address _admin;
modifier onlyOwner {
require(msg.sender == _owner, 'Caller must be owner');
_;
}
modifier onlyAdmin {
require(msg.sender == _admin, 'Caller must be admin');
_;
}
constructor() public {
_owner = msg.sender;
_admin = msg.sender;
}
function setAdmin(address newAdmin) external onlyOwner {
require(newAdmin != address(0x0), 'Invalid wallet address');
require(newAdmin != _admin, 'Must be different from current admin');
_admin = newAdmin;
}
function removeAdmin() external onlyOwner {
_admin = address(0x0);
}
}
文件 9 的 12:SafeMath.sol
pragma solidity ^0.6.8;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
文件 10 的 12:SafeMath64.sol
pragma solidity 0.6.8;
library SafeMath64 {
function add(uint64 a, uint64 b) internal pure returns (uint64) {
uint64 c = a + b;
require(c >= a, 'SafeMath: addition overflow');
return c;
}
function sub(uint64 a, uint64 b) internal pure returns (uint64) {
return sub(a, b, 'SafeMath: subtraction overflow');
}
function sub(
uint64 a,
uint64 b,
string memory errorMessage
) internal pure returns (uint64) {
require(b <= a, errorMessage);
uint64 c = a - b;
return c;
}
function mul(uint64 a, uint64 b) internal pure returns (uint64) {
if (a == 0) {
return 0;
}
uint64 c = a * b;
require(c / a == b, 'SafeMath: multiplication overflow');
return c;
}
function div(uint64 a, uint64 b) internal pure returns (uint64) {
return div(a, b, 'SafeMath: division by zero');
}
function div(
uint64 a,
uint64 b,
string memory errorMessage
) internal pure returns (uint64) {
require(b > 0, errorMessage);
uint64 c = a / b;
return c;
}
}
文件 11 的 12:Signatures.sol
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;
import { ECDSA } from './ECDSA.sol';
import { Enums, Structs } from './Interfaces.sol';
library Signatures {
function isSignatureValid(
bytes32 hash,
bytes memory signature,
address signer
) internal pure returns (bool) {
return
ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer;
}
function getOrderWalletHash(
Structs.Order memory order,
string memory baseSymbol,
string memory quoteSymbol
) internal pure returns (bytes32) {
require(
order.signatureHashVersion == 1,
'Signature hash version must be 1'
);
return
keccak256(
abi.encodePacked(
abi.encodePacked(
order.signatureHashVersion,
order.nonce,
order.walletAddress,
getMarketSymbol(baseSymbol, quoteSymbol),
uint8(order.orderType),
uint8(order.side),
pipToDecimal(order.quantityInPips)
),
abi.encodePacked(
order.isQuantityInQuote,
order.limitPriceInPips > 0
? pipToDecimal(order.limitPriceInPips)
: '',
order.stopPriceInPips > 0
? pipToDecimal(order.stopPriceInPips)
: '',
order.clientOrderId,
uint8(order.timeInForce),
uint8(order.selfTradePrevention),
order.cancelAfter
)
)
);
}
function getWithdrawalWalletHash(Structs.Withdrawal memory withdrawal)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
withdrawal.nonce,
withdrawal.walletAddress,
withdrawal.withdrawalType == Enums.WithdrawalType.BySymbol
? abi.encodePacked(withdrawal.assetSymbol)
: abi.encodePacked(withdrawal.assetAddress),
pipToDecimal(withdrawal.quantityInPips),
withdrawal.autoDispatchEnabled
)
);
}
function getMarketSymbol(string memory baseSymbol, string memory quoteSymbol)
private
pure
returns (string memory)
{
bytes memory baseSymbolBytes = bytes(baseSymbol);
bytes memory hyphenBytes = bytes('-');
bytes memory quoteSymbolBytes = bytes(quoteSymbol);
bytes memory marketSymbolBytes = bytes(
new string(
baseSymbolBytes.length + quoteSymbolBytes.length + hyphenBytes.length
)
);
uint256 i;
uint256 j;
for (i = 0; i < baseSymbolBytes.length; i++) {
marketSymbolBytes[j++] = baseSymbolBytes[i];
}
marketSymbolBytes[j++] = hyphenBytes[0];
for (i = 0; i < quoteSymbolBytes.length; i++) {
marketSymbolBytes[j++] = quoteSymbolBytes[i];
}
return string(marketSymbolBytes);
}
function pipToDecimal(uint256 pips) private pure returns (string memory) {
uint256 copy = pips;
uint256 length;
while (copy != 0) {
length++;
copy /= 10;
}
if (length < 9) {
length = 9;
}
length++;
bytes memory decimal = new bytes(length);
for (uint256 i = length; i > 0; i--) {
if (length - i == 8) {
decimal[i - 1] = bytes1(uint8(46));
} else {
decimal[i - 1] = bytes1(uint8(48 + (pips % 10)));
pips /= 10;
}
}
return string(decimal);
}
}
文件 12 的 12:UUID.sol
pragma solidity 0.6.8;
import { SafeMath64 } from './SafeMath64.sol';
library UUID {
using SafeMath64 for uint64;
function getTimestampInMsFromUuidV1(uint128 uuid)
internal
pure
returns (uint64 msSinceUnixEpoch)
{
uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F;
require(version == 1, 'Must be v1 UUID');
uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000;
uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000;
uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF;
uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow);
msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000).sub(
12219292800000
);
return msSinceUnixEpoch;
}
}
{
"compilationTarget": {
"Exchange.sol": "Exchange"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 49
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ChainPropagationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"index","type":"uint64"},{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":true,"internalType":"string","name":"assetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DispatcherChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"FeeWalletChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint128","name":"nonce","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"timestampInMs","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"OrderNonceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistrationConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"}],"name":"TokenSymbolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":true,"internalType":"string","name":"baseAssetSymbolIndex","type":"string"},{"indexed":true,"internalType":"string","name":"quoteAssetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"tradePriceInPips","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"buyOrderHash","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"sellOrderHash","type":"bytes32"}],"name":"TradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"}],"name":"addTokenSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"confirmTokenRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenByAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenBySymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"sell","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum Enums.OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Structs.Trade","name":"trade","type":"tuple"}],"name":"executeTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"nonce","type":"uint128"}],"name":"invalidateOrderNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint64","name":"timestampInMs","type":"uint64"}],"name":"loadAssetBySymbol","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"bool","name":"isConfirmed","type":"bool"},{"internalType":"uint64","name":"confirmedTimestampInMs","type":"uint64"}],"internalType":"struct Structs.Asset","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInAssetUnitsByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInAssetUnitsBySymbol","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInPipsByAddress","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInPipsBySymbol","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadFeeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"loadPartiallyFilledOrderQuantityInPips","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newChainPropagationPeriod","type":"uint256"}],"name":"setChainPropagationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"setDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum Enums.WithdrawalType","name":"withdrawalType","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address payable","name":"walletAddress","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"uint64","name":"gasFeeInPips","type":"uint64"},{"internalType":"bool","name":"autoDispatchEnabled","type":"bool"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"assetAddress","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"}]