/*
Copyright 2019 dYdX Trading Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;
// File: openzeppelin-solidity/contracts/math/SafeMath.sol
/**
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
// File: openzeppelin-solidity/contracts/ownership/Ownable.sol
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: contracts/protocol/lib/Require.sol
/**
* @title Require
* @author dYdX
*
* Stringifies parameters to pretty-print revert messages. Costs more gas than regular require()
*/
library Require {
// ============ Constants ============
uint256 constant ASCII_ZERO = 48; // '0'
uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10
uint256 constant ASCII_LOWER_EX = 120; // 'x'
bytes2 constant COLON = 0x3a20; // ': '
bytes2 constant COMMA = 0x2c20; // ', '
bytes2 constant LPAREN = 0x203c; // ' <'
byte constant RPAREN = 0x3e; // '>'
uint256 constant FOUR_BIT_MASK = 0xf;
// ============ Library Functions ============
function that(
bool must,
bytes32 file,
bytes32 reason
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason)
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
uint256 payloadA
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
uint256 payloadA,
uint256 payloadB
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
COMMA,
stringify(payloadB),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
address payloadA
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
address payloadA,
uint256 payloadB
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
COMMA,
stringify(payloadB),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
address payloadA,
uint256 payloadB,
uint256 payloadC
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
COMMA,
stringify(payloadB),
COMMA,
stringify(payloadC),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
bytes32 payloadA
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
RPAREN
)
)
);
}
}
function that(
bool must,
bytes32 file,
bytes32 reason,
bytes32 payloadA,
uint256 payloadB,
uint256 payloadC
)
internal
pure
{
if (!must) {
revert(
string(
abi.encodePacked(
stringifyTruncated(file),
COLON,
stringifyTruncated(reason),
LPAREN,
stringify(payloadA),
COMMA,
stringify(payloadB),
COMMA,
stringify(payloadC),
RPAREN
)
)
);
}
}
// ============ Private Functions ============
function stringifyTruncated(
bytes32 input
)
private
pure
returns (bytes memory)
{
// put the input bytes into the result
bytes memory result = abi.encodePacked(input);
// determine the length of the input by finding the location of the last non-zero byte
for (uint256 i = 32; i > 0; ) {
// reverse-for-loops with unsigned integer
/* solium-disable-next-line security/no-modify-for-iter-var */
i--;
// find the last non-zero byte in order to determine the length
if (result[i] != 0) {
uint256 length = i + 1;
/* solium-disable-next-line security/no-inline-assembly */
assembly {
mstore(result, length) // r.length = length;
}
return result;
}
}
// all bytes are zero
return new bytes(0);
}
function stringify(
uint256 input
)
private
pure
returns (bytes memory)
{
if (input == 0) {
return "0";
}
// get the final string length
uint256 j = input;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
// allocate the string
bytes memory bstr = new bytes(length);
// populate the string starting with the least-significant character
j = input;
for (uint256 i = length; i > 0; ) {
// reverse-for-loops with unsigned integer
/* solium-disable-next-line security/no-modify-for-iter-var */
i--;
// take last decimal digit
bstr[i] = byte(uint8(ASCII_ZERO + (j % 10)));
// remove the last decimal digit
j /= 10;
}
return bstr;
}
function stringify(
address input
)
private
pure
returns (bytes memory)
{
uint256 z = uint256(input);
// addresses are "0x" followed by 20 bytes of data which take up 2 characters each
bytes memory result = new bytes(42);
// populate the result with "0x"
result[0] = byte(uint8(ASCII_ZERO));
result[1] = byte(uint8(ASCII_LOWER_EX));
// for each byte (starting from the lowest byte), populate the result with two characters
for (uint256 i = 0; i < 20; i++) {
// each byte takes two characters
uint256 shift = i * 2;
// populate the least-significant character
result[41 - shift] = char(z & FOUR_BIT_MASK);
z = z >> 4;
// populate the most-significant character
result[40 - shift] = char(z & FOUR_BIT_MASK);
z = z >> 4;
}
return result;
}
function stringify(
bytes32 input
)
private
pure
returns (bytes memory)
{
uint256 z = uint256(input);
// bytes32 are "0x" followed by 32 bytes of data which take up 2 characters each
bytes memory result = new bytes(66);
// populate the result with "0x"
result[0] = byte(uint8(ASCII_ZERO));
result[1] = byte(uint8(ASCII_LOWER_EX));
// for each byte (starting from the lowest byte), populate the result with two characters
for (uint256 i = 0; i < 32; i++) {
// each byte takes two characters
uint256 shift = i * 2;
// populate the least-significant character
result[65 - shift] = char(z & FOUR_BIT_MASK);
z = z >> 4;
// populate the most-significant character
result[64 - shift] = char(z & FOUR_BIT_MASK);
z = z >> 4;
}
return result;
}
function char(
uint256 input
)
private
pure
returns (byte)
{
// return ASCII digit (0-9)
if (input < 10) {
return byte(uint8(input + ASCII_ZERO));
}
// return ASCII letter (a-f)
return byte(uint8(input + ASCII_RELATIVE_ZERO));
}
}
// File: contracts/protocol/lib/Math.sol
/**
* @title Math
* @author dYdX
*
* Library for non-standard Math functions
*/
library Math {
using SafeMath for uint256;
// ============ Constants ============
bytes32 constant FILE = "Math";
// ============ Library Functions ============
/*
* Return target * (numerator / denominator).
*/
function getPartial(
uint256 target,
uint256 numerator,
uint256 denominator
)
internal
pure
returns (uint256)
{
return target.mul(numerator).div(denominator);
}
/*
* Return target * (numerator / denominator), but rounded up.
*/
function getPartialRoundUp(
uint256 target,
uint256 numerator,
uint256 denominator
)
internal
pure
returns (uint256)
{
if (target == 0 || numerator == 0) {
// SafeMath will check for zero denominator
return SafeMath.div(0, denominator);
}
return target.mul(numerator).sub(1).div(denominator).add(1);
}
function to128(
uint256 number
)
internal
pure
returns (uint128)
{
uint128 result = uint128(number);
Require.that(
result == number,
FILE,
"Unsafe cast to uint128"
);
return result;
}
function to96(
uint256 number
)
internal
pure
returns (uint96)
{
uint96 result = uint96(number);
Require.that(
result == number,
FILE,
"Unsafe cast to uint96"
);
return result;
}
function to32(
uint256 number
)
internal
pure
returns (uint32)
{
uint32 result = uint32(number);
Require.that(
result == number,
FILE,
"Unsafe cast to uint32"
);
return result;
}
function min(
uint256 a,
uint256 b
)
internal
pure
returns (uint256)
{
return a < b ? a : b;
}
function max(
uint256 a,
uint256 b
)
internal
pure
returns (uint256)
{
return a > b ? a : b;
}
}
// File: contracts/protocol/lib/Types.sol
/**
* @title Types
* @author dYdX
*
* Library for interacting with the basic structs used in Solo
*/
library Types {
using Math for uint256;
// ============ AssetAmount ============
enum AssetDenomination {
Wei, // the amount is denominated in wei
Par // the amount is denominated in par
}
enum AssetReference {
Delta, // the amount is given as a delta from the current value
Target // the amount is given as an exact number to end up at
}
struct AssetAmount {
bool sign; // true if positive
AssetDenomination denomination;
AssetReference ref;
uint256 value;
}
// ============ Par (Principal Amount) ============
/* ... */
// Individual principal amount for an account
struct Par {
bool sign; // true if positive
uint128 value;
}
/* ... */
// ============ Wei (Token Amount) ============
// Individual token amount for an account
struct Wei {
bool sign; // true if positive
uint256 value;
}
/* ... */
}
// File: contracts/protocol/lib/Account.sol
/**
* @title Account
* @author dYdX
*
* Library of structs and functions that represent an account
*/
library Account {
// ============ Enums ============
/*
* Most-recently-cached account status.
*
* Normal: Can only be liquidated if the account values are violating the global margin-ratio.
* Liquid: Can be liquidated no matter the account values.
* Can be vaporized if there are no more positive account values.
* Vapor: Has only negative (or zeroed) account values. Can be vaporized.
*
*/
enum Status {
Normal,
Liquid,
Vapor
}
// ============ Structs ============
// Represents the unique key that specifies an account
struct Info {
address owner; // The address that owns the account
uint256 number; // A nonce that allows a single address to control many accounts
}
/* ... */
}
// File: contracts/protocol/lib/Actions.sol
/**
* @title Actions
* @author dYdX
*
* Library that defines and parses valid Actions
*/
library Actions {
// ============ Constants ============
bytes32 constant FILE = "Actions";
// ============ Enums ============
enum ActionType {
Deposit, // supply tokens
Withdraw, // borrow tokens
Transfer, // transfer balance between accounts
Buy, // buy an amount of some token (externally)
Sell, // sell an amount of some token (externally)
Trade, // trade tokens against another account
Liquidate, // liquidate an undercollateralized or expiring account
Vaporize, // use excess tokens to zero-out a completely negative account
Call // send arbitrary data to an address
}
enum AccountLayout {
OnePrimary,
TwoPrimary,
PrimaryAndSecondary
}
enum MarketLayout {
ZeroMarkets,
OneMarket,
TwoMarkets
}
// ============ Structs ============
/*
* Arguments that are passed to Solo in an ordered list as part of a single operation.
* Each ActionArgs has an actionType which specifies which action struct that this data will be
* parsed into before being processed.
*/
struct ActionArgs {
ActionType actionType;
uint256 accountId;
Types.AssetAmount amount;
uint256 primaryMarketId;
uint256 secondaryMarketId;
address otherAddress;
uint256 otherAccountId;
bytes data;
}
// ============ Helper Functions ============
function getMarketLayout(
ActionType actionType
)
internal
pure
returns (MarketLayout)
{
if (
actionType == Actions.ActionType.Deposit
|| actionType == Actions.ActionType.Withdraw
|| actionType == Actions.ActionType.Transfer
) {
return MarketLayout.OneMarket;
}
else if (actionType == Actions.ActionType.Call) {
return MarketLayout.ZeroMarkets;
}
return MarketLayout.TwoMarkets;
}
function getAccountLayout(
ActionType actionType
)
internal
pure
returns (AccountLayout)
{
if (
actionType == Actions.ActionType.Transfer
|| actionType == Actions.ActionType.Trade
) {
return AccountLayout.TwoPrimary;
} else if (
actionType == Actions.ActionType.Liquidate
|| actionType == Actions.ActionType.Vaporize
) {
return AccountLayout.PrimaryAndSecondary;
}
return AccountLayout.OnePrimary;
}
/* ... */
}
// File: contracts/protocol/SoloMargin.sol
/**
* @title SoloMargin
* @author dYdX
*
* Main contract that inherits from other contracts
*/
contract SoloMargin {
// ============ Public Functions ============
/**
* The main entry-point to Solo that allows users and contracts to manage accounts.
* Take one or more actions on one or more accounts. The msg.sender must be the owner or
* operator of all accounts except for those being liquidated, vaporized, or traded with.
* One call to operate() is considered a singular "operation". Account collateralization is
* ensured only after the completion of the entire operation.
*
* @param accounts A list of all accounts that will be used in this operation. Cannot contain
* duplicates. In each action, the relevant account will be referred-to by its
* index in the list.
* @param actions An ordered list of all actions that will be taken in this operation. The
* actions will be processed in order.
*/
function operate(
Account.Info[] memory accounts,
Actions.ActionArgs[] memory actions
)
public;
// ============ Getters for Permissions ============
/**
* Return true if a particular address is approved as an operator for an owner's accounts.
* Approved operators can act on the accounts of the owner as if it were the operator's own.
*
* @param owner The owner of the accounts
* @param operator The possible operator
* @return True if operator is approved for owner's accounts
*/
function getIsLocalOperator(
address owner,
address operator
)
public
view
returns (bool);
/* ... */
}
// File: contracts/external/helpers/OnlySolo.sol
/**
* @title OnlySolo
* @author dYdX
*
* Inheritable contract that restricts the calling of certain functions to Solo only
*/
contract OnlySolo {
// ============ Constants ============
bytes32 constant FILE = "OnlySolo";
// ============ Storage ============
SoloMargin public SOLO_MARGIN;
// ============ Constructor ============
constructor (
address soloMargin
)
public
{
SOLO_MARGIN = SoloMargin(soloMargin);
}
// ============ Modifiers ============
modifier onlySolo(address from) {
Require.that(
from == address(SOLO_MARGIN),
FILE,
"Only Solo can call function",
from
);
_;
}
}
// File: contracts/external/lib/TypedSignature.sol
/**
* @title TypedSignature
* @author dYdX
*
* Library to unparse typed signatures
*/
library TypedSignature {
// ============ Constants ============
bytes32 constant private FILE = "TypedSignature";
// prepended message with the length of the signed hash in decimal
bytes constant private PREPEND_DEC = "\x19Ethereum Signed Message:\n32";
// prepended message with the length of the signed hash in hexadecimal
bytes constant private PREPEND_HEX = "\x19Ethereum Signed Message:\n\x20";
// Number of bytes in a typed signature
uint256 constant private NUM_SIGNATURE_BYTES = 66;
// ============ Enums ============
enum SignatureType {
NoPrepend,
Decimal,
Hexadecimal,
Invalid
}
// ============ Functions ============
/**
* Gives the address of the signer of a hash. Also allows for the commonly prepended string of
* '\x19Ethereum Signed Message:\n' + message.length
*
* @param hash Hash that was signed (does not include prepended message)
* @param signatureWithType Type and ECDSA signature with structure: {32:r}{32:s}{1:v}{1:type}
* @return address of the signer of the hash
*/
function recover(
bytes32 hash,
bytes memory signatureWithType
)
internal
pure
returns (address)
{
Require.that(
signatureWithType.length == NUM_SIGNATURE_BYTES,
FILE,
"Invalid signature length"
);
bytes32 r;
bytes32 s;
uint8 v;
uint8 rawSigType;
/* solium-disable-next-line security/no-inline-assembly */
assembly {
r := mload(add(signatureWithType, 0x20))
s := mload(add(signatureWithType, 0x40))
let lastSlot := mload(add(signatureWithType, 0x60))
v := byte(0, lastSlot)
rawSigType := byte(1, lastSlot)
}
Require.that(
rawSigType < uint8(SignatureType.Invalid),
FILE,
"Invalid signature type"
);
SignatureType sigType = SignatureType(rawSigType);
bytes32 signedHash;
if (sigType == SignatureType.NoPrepend) {
signedHash = hash;
} else if (sigType == SignatureType.Decimal) {
signedHash = keccak256(abi.encodePacked(PREPEND_DEC, hash));
} else {
assert(sigType == SignatureType.Hexadecimal);
signedHash = keccak256(abi.encodePacked(PREPEND_HEX, hash));
}
return ecrecover(
signedHash,
v,
r,
s
);
}
}
// File: contracts/external/proxies/SignedOperationProxy.sol
/**
* @title SignedOperationProxy
* @author dYdX
*
* Contract for sending operations on behalf of others
*/
contract SignedOperationProxy is
OnlySolo,
Ownable
{
using SafeMath for uint256;
// ============ Constants ============
bytes32 constant private FILE = "SignedOperationProxy";
// EIP191 header for EIP712 prefix
bytes2 constant private EIP191_HEADER = 0x1901;
// EIP712 Domain Name value
string constant private EIP712_DOMAIN_NAME = "SignedOperationProxy";
// EIP712 Domain Version value
string constant private EIP712_DOMAIN_VERSION = "1.1";
// EIP712 encodeType of EIP712Domain
bytes constant private EIP712_DOMAIN_STRING = abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
);
// EIP712 encodeType of Operation
bytes constant private EIP712_OPERATION_STRING = abi.encodePacked(
"Operation(",
"Action[] actions,",
"uint256 expiration,",
"uint256 salt,",
"address sender,",
"address signer",
")"
);
// EIP712 encodeType of Action
bytes constant private EIP712_ACTION_STRING = abi.encodePacked(
"Action(",
"uint8 actionType,",
"address accountOwner,",
"uint256 accountNumber,",
"AssetAmount assetAmount,",
"uint256 primaryMarketId,",
"uint256 secondaryMarketId,",
"address otherAddress,",
"address otherAccountOwner,",
"uint256 otherAccountNumber,",
"bytes data",
")"
);
// EIP712 encodeType of AssetAmount
bytes constant private EIP712_ASSET_AMOUNT_STRING = abi.encodePacked(
"AssetAmount(",
"bool sign,",
"uint8 denomination,",
"uint8 ref,",
"uint256 value",
")"
);
// EIP712 typeHash of EIP712Domain
/* solium-disable-next-line indentation */
bytes32 constant private EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
EIP712_DOMAIN_STRING
));
// EIP712 typeHash of Operation
/* solium-disable-next-line indentation */
bytes32 constant private EIP712_OPERATION_HASH = keccak256(abi.encodePacked(
EIP712_OPERATION_STRING,
EIP712_ACTION_STRING,
EIP712_ASSET_AMOUNT_STRING
));
// EIP712 typeHash of Action
/* solium-disable-next-line indentation */
bytes32 constant private EIP712_ACTION_HASH = keccak256(abi.encodePacked(
EIP712_ACTION_STRING,
EIP712_ASSET_AMOUNT_STRING
));
// EIP712 typeHash of AssetAmount
/* solium-disable-next-line indentation */
bytes32 constant private EIP712_ASSET_AMOUNT_HASH = keccak256(abi.encodePacked(
EIP712_ASSET_AMOUNT_STRING
));
// ============ Structs ============
struct OperationHeader {
uint256 expiration;
uint256 salt;
address sender;
address signer;
}
struct Authorization {
uint256 numActions;
OperationHeader header;
bytes signature;
}
// ============ Events ============
event ContractStatusSet(
bool operational
);
event LogOperationExecuted(
bytes32 indexed operationHash,
address indexed signer,
address indexed sender
);
event LogOperationCanceled(
bytes32 indexed operationHash,
address indexed canceler
);
// ============ Immutable Storage ============
// Hash of the EIP712 Domain Separator data
bytes32 public EIP712_DOMAIN_HASH;
// ============ Mutable Storage ============
// true if this contract can process operationss
bool public g_isOperational;
// operation hash => was executed (or canceled)
mapping (bytes32 => bool) public g_invalidated;
// ============ Constructor ============
constructor (
address soloMargin,
uint256 chainId
)
public
OnlySolo(soloMargin)
{
g_isOperational = true;
/* solium-disable-next-line indentation */
EIP712_DOMAIN_HASH = keccak256(abi.encode(
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
keccak256(bytes(EIP712_DOMAIN_NAME)),
keccak256(bytes(EIP712_DOMAIN_VERSION)),
chainId,
address(this)
));
}
// ============ Admin Functions ============
/**
* The owner can shut down the exchange.
*/
function shutDown()
external
onlyOwner
{
g_isOperational = false;
emit ContractStatusSet(false);
}
/**
* The owner can start back up the exchange.
*/
function startUp()
external
onlyOwner
{
g_isOperational = true;
emit ContractStatusSet(true);
}
// ============ Public Functions ============
/**
* Allows a signer to permanently cancel an operation on-chain.
*
* @param accounts The accounts involved in the operation
* @param actions The actions involved in the operation
* @param auth The unsigned authorization of the operation
*/
function cancel(
Account.Info[] memory accounts,
Actions.ActionArgs[] memory actions,
Authorization memory auth
)
public
{
bytes32 operationHash = getOperationHash(
accounts,
actions,
auth,
0
);
Require.that(
auth.header.signer == msg.sender,
FILE,
"Canceler must be signer"
);
g_invalidated[operationHash] = true;
emit LogOperationCanceled(operationHash, msg.sender);
}
/**
* Submits an operation to SoloMargin. Actions for accounts that the msg.sender does not control
* must be authorized by a signed message. Each authorization can apply to multiple actions at
* once which must occur in-order next to each other. An empty authorization must be supplied
* explicitly for each group of actions that do not require a signed message.
*
* @param accounts The accounts to forward to SoloMargin.operate()
* @param actions The actions to forward to SoloMargin.operate()
* @param auths The signed authorizations for each group of actions
* (or unsigned if msg.sender is already authorized)
*/
function operate(
Account.Info[] memory accounts,
Actions.ActionArgs[] memory actions,
Authorization[] memory auths
)
public
{
Require.that(
g_isOperational,
FILE,
"Contract is not operational"
);
// cache the index of the first action for this auth
uint256 actionStartIdx = 0;
// loop over all auths
for (uint256 authIdx = 0; authIdx < auths.length; authIdx++) {
Authorization memory auth = auths[authIdx];
// require that the message is not expired
Require.that(
auth.header.expiration == 0 || auth.header.expiration >= block.timestamp,
FILE,
"Signed operation is expired",
authIdx
);
// require that the sender matches the authorization
Require.that(
auth.header.sender == address(0) || auth.header.sender == msg.sender,
FILE,
"Operation sender mismatch",
authIdx
);
// consider the signer to be msg.sender unless there is a signature
address signer = msg.sender;
// if there is a signature, then validate it
if (auth.signature.length != 0) {
// get the hash of the operation
bytes32 operationHash = getOperationHash(
accounts,
actions,
auth,
actionStartIdx
);
// require that this message is still valid
Require.that(
!g_invalidated[operationHash],
FILE,
"Hash already used or canceled",
operationHash
);
// get the signer
signer = TypedSignature.recover(operationHash, auth.signature);
// require that this signer matches the authorization
Require.that(
auth.header.signer == signer,
FILE,
"Invalid signature"
);
// consider this operationHash to be used (and therefore no longer valid)
g_invalidated[operationHash] = true;
emit LogOperationExecuted(operationHash, signer, msg.sender);
}
// cache the index of the first action after this auth
uint256 actionEndIdx = actionStartIdx.add(auth.numActions);
// loop over all actions for which this auth applies
for (uint256 actionIdx = actionStartIdx; actionIdx < actionEndIdx; actionIdx++) {
// validate primary account
Actions.ActionArgs memory action = actions[actionIdx];
validateAccountOwner(accounts[action.accountId].owner, signer);
// validate second account in the case of a transfer
if (action.actionType == Actions.ActionType.Transfer) {
validateAccountOwner(accounts[action.otherAccountId].owner, signer);
}
}
// update actionStartIdx
actionStartIdx = actionEndIdx;
}
// require that all actions are signed or from msg.sender
Require.that(
actionStartIdx == actions.length,
FILE,
"Not all actions are signed"
);
// send the operation
SOLO_MARGIN.operate(accounts, actions);
}
// ============ Getters ============
/**
* Returns a bool for each operation. True if the operation is invalid (from being canceled or
* previously executed).
*/
function getOperationsAreInvalid(
bytes32[] memory operationHashes
)
public
view
returns(bool[] memory)
{
uint256 numOperations = operationHashes.length;
bool[] memory output = new bool[](numOperations);
for (uint256 i = 0; i < numOperations; i++) {
output[i] = g_invalidated[operationHashes[i]];
}
return output;
}
// ============ Private Helper Functions ============
/**
* Validates that either the signer or the msg.sender are the accountOwner (or that either are
* localOperators of the accountOwner).
*/
function validateAccountOwner(
address accountOwner,
address signer
)
private
view
{
bool valid =
msg.sender == accountOwner
|| signer == accountOwner
|| SOLO_MARGIN.getIsLocalOperator(accountOwner, msg.sender)
|| SOLO_MARGIN.getIsLocalOperator(accountOwner, signer);
Require.that(
valid,
FILE,
"Signer not authorized",
signer
);
}
/**
* Returns the EIP712 hash of an Operation message.
*/
function getOperationHash(
Account.Info[] memory accounts,
Actions.ActionArgs[] memory actions,
Authorization memory auth,
uint256 startIdx
)
private
view
returns (bytes32)
{
// get the bytes32 hash of each action, then packed together
bytes32 actionsEncoding = getActionsEncoding(
accounts,
actions,
auth,
startIdx
);
// compute the EIP712 hashStruct of an Operation struct
/* solium-disable-next-line indentation */
bytes32 structHash = keccak256(abi.encode(
EIP712_OPERATION_HASH,
actionsEncoding,
auth.header
));
// compute eip712 compliant hash
/* solium-disable-next-line indentation */
return keccak256(abi.encodePacked(
EIP191_HEADER,
EIP712_DOMAIN_HASH,
structHash
));
}
/**
* Returns the EIP712 encodeData of an Action struct array.
*/
function getActionsEncoding(
Account.Info[] memory accounts,
Actions.ActionArgs[] memory actions,
Authorization memory auth,
uint256 startIdx
)
private
pure
returns (bytes32)
{
// store hash of each action
bytes32[] memory actionsBytes = new bytes32[](auth.numActions);
// for each action that corresponds to the auth
for (uint256 i = 0; i < auth.numActions; i++) {
Actions.ActionArgs memory action = actions[startIdx + i];
// if action type has no second account, assume null account
Account.Info memory otherAccount =
(Actions.getAccountLayout(action.actionType) == Actions.AccountLayout.OnePrimary)
? Account.Info({ owner: address(0), number: 0 })
: accounts[action.otherAccountId];
// compute the individual hash for the action
/* solium-disable-next-line indentation */
actionsBytes[i] = getActionHash(
action,
accounts[action.accountId],
otherAccount
);
}
return keccak256(abi.encodePacked(actionsBytes));
}
/**
* Returns the EIP712 hashStruct of an Action struct.
*/
function getActionHash(
Actions.ActionArgs memory action,
Account.Info memory primaryAccount,
Account.Info memory secondaryAccount
)
private
pure
returns (bytes32)
{
/* solium-disable-next-line indentation */
return keccak256(abi.encode(
EIP712_ACTION_HASH,
action.actionType,
primaryAccount.owner,
primaryAccount.number,
getAssetAmountHash(action.amount),
action.primaryMarketId,
action.secondaryMarketId,
action.otherAddress,
secondaryAccount.owner,
secondaryAccount.number,
keccak256(action.data)
));
}
/**
* Returns the EIP712 hashStruct of an AssetAmount struct.
*/
function getAssetAmountHash(
Types.AssetAmount memory amount
)
private
pure
returns (bytes32)
{
/* solium-disable-next-line indentation */
return keccak256(abi.encode(
EIP712_ASSET_AMOUNT_HASH,
amount.sign,
amount.denomination,
amount.ref,
amount.value
));
}
}
{
"compilationTarget": {
"SignedOperationProxy.sol": "SignedOperationProxy"
},
"evmVersion": "petersburg",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": []
}
[{"constant":false,"inputs":[],"name":"shutDown","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SOLO_MARGIN","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"startUp","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"operationHashes","type":"bytes32[]"}],"name":"getOperationsAreInvalid","outputs":[{"name":"","type":"bool[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"g_invalidated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"owner","type":"address"},{"name":"number","type":"uint256"}],"name":"accounts","type":"tuple[]"},{"components":[{"name":"actionType","type":"uint8"},{"name":"accountId","type":"uint256"},{"components":[{"name":"sign","type":"bool"},{"name":"denomination","type":"uint8"},{"name":"ref","type":"uint8"},{"name":"value","type":"uint256"}],"name":"amount","type":"tuple"},{"name":"primaryMarketId","type":"uint256"},{"name":"secondaryMarketId","type":"uint256"},{"name":"otherAddress","type":"address"},{"name":"otherAccountId","type":"uint256"},{"name":"data","type":"bytes"}],"name":"actions","type":"tuple[]"},{"components":[{"name":"numActions","type":"uint256"},{"components":[{"name":"expiration","type":"uint256"},{"name":"salt","type":"uint256"},{"name":"sender","type":"address"},{"name":"signer","type":"address"}],"name":"header","type":"tuple"},{"name":"signature","type":"bytes"}],"name":"auths","type":"tuple[]"}],"name":"operate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"owner","type":"address"},{"name":"number","type":"uint256"}],"name":"accounts","type":"tuple[]"},{"components":[{"name":"actionType","type":"uint8"},{"name":"accountId","type":"uint256"},{"components":[{"name":"sign","type":"bool"},{"name":"denomination","type":"uint8"},{"name":"ref","type":"uint8"},{"name":"value","type":"uint256"}],"name":"amount","type":"tuple"},{"name":"primaryMarketId","type":"uint256"},{"name":"secondaryMarketId","type":"uint256"},{"name":"otherAddress","type":"address"},{"name":"otherAccountId","type":"uint256"},{"name":"data","type":"bytes"}],"name":"actions","type":"tuple[]"},{"components":[{"name":"numActions","type":"uint256"},{"components":[{"name":"expiration","type":"uint256"},{"name":"salt","type":"uint256"},{"name":"sender","type":"address"},{"name":"signer","type":"address"}],"name":"header","type":"tuple"},{"name":"signature","type":"bytes"}],"name":"auth","type":"tuple"}],"name":"cancel","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"EIP712_DOMAIN_HASH","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"g_isOperational","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"soloMargin","type":"address"},{"name":"chainId","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operational","type":"bool"}],"name":"ContractStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operationHash","type":"bytes32"},{"indexed":true,"name":"signer","type":"address"},{"indexed":true,"name":"sender","type":"address"}],"name":"LogOperationExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operationHash","type":"bytes32"},{"indexed":true,"name":"canceler","type":"address"}],"name":"LogOperationCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]