@dolomite-exchange/dolomite-margin
Version:
Ethereum Smart Contracts and TypeScript library used for the DolomiteMargin trading protocol
511 lines • 27.6 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignedOperations = void 0;
var web3_1 = __importDefault(require("web3"));
var Signer_1 = require("./Signer");
var Constants_1 = require("../lib/Constants");
var Helpers_1 = require("../lib/Helpers");
var BytesHelper_1 = require("../lib/BytesHelper");
var SignatureHelper_1 = require("../lib/SignatureHelper");
var types_1 = require("../types");
var EIP712_OPERATION_STRUCT = [
{ type: 'Action[]', name: 'actions' },
{ type: 'uint256', name: 'expiration' },
{ type: 'uint256', name: 'salt' },
{ type: 'address', name: 'sender' },
{ type: 'address', name: 'signer' },
];
var EIP712_ACTION_STRUCT = [
{ type: 'uint8', name: 'actionType' },
{ type: 'address', name: 'accountOwner' },
{ type: 'uint256', name: 'accountNumber' },
{ type: 'AssetAmount', name: 'assetAmount' },
{ type: 'uint256', name: 'primaryMarketId' },
{ type: 'uint256', name: 'secondaryMarketId' },
{ type: 'address', name: 'otherAddress' },
{ type: 'address', name: 'otherAccountOwner' },
{ type: 'uint256', name: 'otherAccountNumber' },
{ type: 'bytes', name: 'data' },
];
var EIP712_ASSET_AMOUNT_STRUCT = [
{ type: 'bool', name: 'sign' },
{ type: 'uint8', name: 'denomination' },
{ type: 'uint8', name: 'ref' },
{ type: 'uint256', name: 'value' },
];
var EIP712_ASSET_AMOUNT_STRING = 'AssetAmount(' +
'bool sign,' +
'uint8 denomination,' +
'uint8 ref,' +
'uint256 value' +
')';
var EIP712_ACTION_STRING = 'Action(' + // tslint:disable-line
'uint8 actionType,' +
'address accountOwner,' +
'uint256 accountNumber,' +
'AssetAmount assetAmount,' +
'uint256 primaryMarketId,' +
'uint256 secondaryMarketId,' +
'address otherAddress,' +
'address otherAccountOwner,' +
'uint256 otherAccountNumber,' +
'bytes data' +
')' +
EIP712_ASSET_AMOUNT_STRING;
var EIP712_OPERATION_STRING = 'Operation(' + // tslint:disable-line
'Action[] actions,' +
'uint256 expiration,' +
'uint256 salt,' +
'address sender,' +
'address signer' +
')' +
EIP712_ACTION_STRING;
var EIP712_CANCEL_OPERATION_STRUCT = [
{ type: 'string', name: 'action' },
{ type: 'bytes32[]', name: 'operationHashes' },
];
var EIP712_CANCEL_OPERATION_STRUCT_STRING = 'CancelOperation(' + 'string action,' + 'bytes32[] operationHashes' + ')';
var SignedOperations = /** @class */ (function (_super) {
__extends(SignedOperations, _super);
// ============ Constructor ============
function SignedOperations(contracts, web3, networkId) {
var _this = _super.call(this, web3) || this;
_this.contracts = contracts;
_this.networkId = networkId;
return _this;
}
// ============ On-Chain Cancel ============
/**
* Sends an transaction to cancel an operation on-chain.
*/
SignedOperations.prototype.cancelOperation = function (operation, options) {
return __awaiter(this, void 0, void 0, function () {
var accounts, actions, getAccountId, i, action;
return __generator(this, function (_a) {
accounts = [];
actions = [];
getAccountId = function (accountOwner, accountNumber) {
if (accountOwner === Constants_1.ADDRESSES.ZERO) {
return 0;
}
var accountInfo = {
owner: accountOwner,
number: accountNumber.toFixed(0),
};
var index = accounts.indexOf(accountInfo);
if (index >= 0) {
return index;
}
accounts.push(accountInfo);
return accounts.length - 1;
};
for (i = 0; i < operation.actions.length; i += 1) {
action = operation.actions[i];
actions.push({
accountId: getAccountId(action.primaryAccountOwner, action.primaryAccountNumber),
actionType: action.actionType,
primaryMarketId: Helpers_1.toString(action.primaryMarketId),
secondaryMarketId: Helpers_1.toString(action.secondaryMarketId),
otherAddress: action.otherAddress,
otherAccountId: getAccountId(action.secondaryAccountOwner, action.secondaryAccountNumber),
data: BytesHelper_1.hexStringToBytes(action.data),
amount: {
sign: action.amount.sign,
ref: Helpers_1.toString(action.amount.ref),
denomination: Helpers_1.toString(action.amount.denomination),
value: Helpers_1.toString(action.amount.value),
},
});
}
return [2 /*return*/, this.contracts.callContractFunction(this.contracts.signedOperationProxy.methods.cancel(accounts, actions, {
numActions: operation.actions.length.toString(),
header: {
expiration: operation.expiration.toFixed(0),
salt: operation.salt.toFixed(0),
sender: operation.sender,
signer: operation.signer,
},
signature: [],
}), options || { from: operation.signer })];
});
});
};
// ============ Getter Contract Methods ============
/**
* Returns true if the contract can process operations.
*/
SignedOperations.prototype.isOperational = function (options) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.contracts.callConstantContractFunction(this.contracts.signedOperationProxy.methods.g_isOperational(), options)];
});
});
};
/**
* Gets the status and the current filled amount (in makerAmount) of all given orders.
*/
SignedOperations.prototype.getOperationsAreInvalid = function (operations, options) {
return __awaiter(this, void 0, void 0, function () {
var hashes;
var _this = this;
return __generator(this, function (_a) {
hashes = operations.map(function (operation) {
return _this.getOperationHash(operation);
});
return [2 /*return*/, this.contracts.callConstantContractFunction(this.contracts.signedOperationProxy.methods.getOperationsAreInvalid(hashes), options)];
});
});
};
// ============ Signing Methods ============
/**
* Sends operation to current provider for signing. Can sign locally if the signing account is
* loaded into web3 and SigningMethod.Hash is used.
*/
SignedOperations.prototype.signOperation = function (operation, signingMethod) {
return __awaiter(this, void 0, void 0, function () {
var _a, hash, rawSignature, hashSig, unsafeHashSig;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = signingMethod;
switch (_a) {
case types_1.SigningMethod.Hash: return [3 /*break*/, 1];
case types_1.SigningMethod.UnsafeHash: return [3 /*break*/, 1];
case types_1.SigningMethod.Compatibility: return [3 /*break*/, 1];
case types_1.SigningMethod.TypedData: return [3 /*break*/, 3];
case types_1.SigningMethod.MetaMask: return [3 /*break*/, 3];
case types_1.SigningMethod.MetaMaskLatest: return [3 /*break*/, 3];
case types_1.SigningMethod.CoinbaseWallet: return [3 /*break*/, 3];
}
return [3 /*break*/, 4];
case 1:
hash = this.getOperationHash(operation);
return [4 /*yield*/, this.web3.eth.sign(hash, operation.signer)];
case 2:
rawSignature = _b.sent();
hashSig = SignatureHelper_1.createTypedSignature(rawSignature, SignatureHelper_1.SIGNATURE_TYPES.DECIMAL);
if (signingMethod === types_1.SigningMethod.Hash) {
return [2 /*return*/, hashSig];
}
unsafeHashSig = SignatureHelper_1.createTypedSignature(rawSignature, SignatureHelper_1.SIGNATURE_TYPES.NO_PREPEND);
if (signingMethod === types_1.SigningMethod.UnsafeHash) {
return [2 /*return*/, unsafeHashSig];
}
if (this.operationByHashHasValidSignature(hash, unsafeHashSig, operation.signer)) {
return [2 /*return*/, unsafeHashSig];
}
return [2 /*return*/, hashSig];
case 3: return [2 /*return*/, this.ethSignTypedOperationInternal(operation, signingMethod)];
case 4: throw new Error("Invalid signing method " + signingMethod);
}
});
});
};
/**
* Sends operation to current provider for signing of a cancel message. Can sign locally if the
* signing account is loaded into web3 and SigningMethod.Hash is used.
*/
SignedOperations.prototype.signCancelOperation = function (operation, signingMethod) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.signCancelOperationByHash(this.getOperationHash(operation), operation.signer, signingMethod)];
});
});
};
/**
* Sends operationHash to current provider for signing of a cancel message. Can sign locally if
* the signing account is loaded into web3 and SigningMethod.Hash is used.
*/
SignedOperations.prototype.signCancelOperationByHash = function (operationHash, signer, signingMethod) {
return __awaiter(this, void 0, void 0, function () {
var _a, cancelHash, rawSignature, hashSig, unsafeHashSig;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = signingMethod;
switch (_a) {
case types_1.SigningMethod.Hash: return [3 /*break*/, 1];
case types_1.SigningMethod.UnsafeHash: return [3 /*break*/, 1];
case types_1.SigningMethod.Compatibility: return [3 /*break*/, 1];
case types_1.SigningMethod.TypedData: return [3 /*break*/, 3];
case types_1.SigningMethod.MetaMask: return [3 /*break*/, 3];
case types_1.SigningMethod.MetaMaskLatest: return [3 /*break*/, 3];
case types_1.SigningMethod.CoinbaseWallet: return [3 /*break*/, 3];
}
return [3 /*break*/, 4];
case 1:
cancelHash = this.operationHashToCancelOperationHash(operationHash);
return [4 /*yield*/, this.web3.eth.sign(cancelHash, signer)];
case 2:
rawSignature = _b.sent();
hashSig = SignatureHelper_1.createTypedSignature(rawSignature, SignatureHelper_1.SIGNATURE_TYPES.DECIMAL);
if (signingMethod === types_1.SigningMethod.Hash) {
return [2 /*return*/, hashSig];
}
unsafeHashSig = SignatureHelper_1.createTypedSignature(rawSignature, SignatureHelper_1.SIGNATURE_TYPES.NO_PREPEND);
if (signingMethod === types_1.SigningMethod.UnsafeHash) {
return [2 /*return*/, unsafeHashSig];
}
if (this.cancelOperationByHashHasValidSignature(operationHash, unsafeHashSig, signer)) {
return [2 /*return*/, unsafeHashSig];
}
return [2 /*return*/, hashSig];
case 3: return [2 /*return*/, this.ethSignTypedCancelOperationInternal(operationHash, signer, signingMethod)];
case 4: throw new Error("Invalid signing method " + signingMethod);
}
});
});
};
// ============ Signing Cancel Operation Methods ============
// noinspection JSUnusedGlobalSymbols
/**
* Uses web3.eth.sign to sign a cancel message for an operation. This signature is not used
* on-chain,but allows backend services to verify that the cancel operation api call is from
* the original maker of the operation.
*/
SignedOperations.prototype.ethSignCancelOperation = function (operation) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.ethSignCancelOperationByHash(this.getOperationHash(operation), operation.signer)];
});
});
};
/**
* Uses web3.eth.sign to sign a cancel message for an operation hash. This signature is not used
* on-chain, but allows dYdX backend services to verify that the cancel operation api call is from
* the original maker of the operation.
*/
SignedOperations.prototype.ethSignCancelOperationByHash = function (operationHash, signer) {
return __awaiter(this, void 0, void 0, function () {
var cancelHash, signature;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cancelHash = this.operationHashToCancelOperationHash(operationHash);
return [4 /*yield*/, this.web3.eth.sign(cancelHash, signer)];
case 1:
signature = _a.sent();
return [2 /*return*/, SignatureHelper_1.createTypedSignature(signature, SignatureHelper_1.SIGNATURE_TYPES.DECIMAL)];
}
});
});
};
// ============ Signature Verification ============
/**
* Returns true if the operation object has a non-null valid signature from the maker of the
* operation.
*/
SignedOperations.prototype.operationHasValidSignature = function (signedOperation) {
return this.operationByHashHasValidSignature(this.getOperationHash(signedOperation), signedOperation.typedSignature, signedOperation.signer);
};
/**
* Returns true if the operation hash has a non-null valid signature from a particular signer.
*/
SignedOperations.prototype.operationByHashHasValidSignature = function (operationHash, typedSignature, expectedSigner) {
var signer = SignatureHelper_1.ecRecoverTypedSignature(operationHash, typedSignature);
return BytesHelper_1.addressesAreEqual(signer, expectedSigner);
};
/**
* Returns true if the cancel operation message has a valid signature.
*/
SignedOperations.prototype.cancelOperationHasValidSignature = function (operation, typedSignature) {
return this.cancelOperationByHashHasValidSignature(this.getOperationHash(operation), typedSignature, operation.signer);
};
/**
* Returns true if the cancel operation message has a valid signature.
*/
SignedOperations.prototype.cancelOperationByHashHasValidSignature = function (operationHash, typedSignature, expectedSigner) {
var cancelHash = this.operationHashToCancelOperationHash(operationHash);
var signer = SignatureHelper_1.ecRecoverTypedSignature(cancelHash, typedSignature);
return BytesHelper_1.addressesAreEqual(signer, expectedSigner);
};
// ============ Hashing Functions ============
/**
* Returns the final signable EIP712 hash for approving an operation.
*/
SignedOperations.prototype.getOperationHash = function (operation) {
var structHash = web3_1.default.utils.soliditySha3({ t: 'bytes32', v: BytesHelper_1.hashString(EIP712_OPERATION_STRING) }, { t: 'bytes32', v: this.getActionsHash(operation.actions) }, { t: 'uint256', v: Helpers_1.toString(operation.expiration) }, { t: 'uint256', v: Helpers_1.toString(operation.salt) }, { t: 'bytes32', v: BytesHelper_1.addressToBytes32(operation.sender) }, { t: 'bytes32', v: BytesHelper_1.addressToBytes32(operation.signer) });
return this.getEIP712Hash(structHash);
};
/**
* Returns the EIP712 hash of the actions array.
*/
SignedOperations.prototype.getActionsHash = function (actions) {
var _this = this;
var actionsAsHashes = actions.length
? actions
.map(function (action) { return BytesHelper_1.stripHexPrefix(_this.getActionHash(action)); })
.join('')
: '';
return BytesHelper_1.hashBytes(actionsAsHashes);
};
/**
* Returns the EIP712 hash of a single Action struct.
*/
SignedOperations.prototype.getActionHash = function (action) {
return web3_1.default.utils.soliditySha3({ t: 'bytes32', v: BytesHelper_1.hashString(EIP712_ACTION_STRING) }, { t: 'uint256', v: Helpers_1.toString(action.actionType) }, { t: 'bytes32', v: BytesHelper_1.addressToBytes32(action.primaryAccountOwner) }, { t: 'uint256', v: Helpers_1.toString(action.primaryAccountNumber) }, { t: 'bytes32', v: this.getAssetAmountHash(action.amount) }, { t: 'uint256', v: Helpers_1.toString(action.primaryMarketId) }, { t: 'uint256', v: Helpers_1.toString(action.secondaryMarketId) }, { t: 'bytes32', v: BytesHelper_1.addressToBytes32(action.otherAddress) }, { t: 'bytes32', v: BytesHelper_1.addressToBytes32(action.secondaryAccountOwner) }, { t: 'uint256', v: Helpers_1.toString(action.secondaryAccountNumber) }, { t: 'bytes32', v: BytesHelper_1.hashBytes(action.data) });
};
/**
* Returns the EIP712 hash of an AssetAmount struct.
*/
SignedOperations.prototype.getAssetAmountHash = function (amount) {
return web3_1.default.utils.soliditySha3({ t: 'bytes32', v: BytesHelper_1.hashString(EIP712_ASSET_AMOUNT_STRING) }, { t: 'uint256', v: Helpers_1.toString(amount.sign ? 1 : 0) }, { t: 'uint256', v: Helpers_1.toString(amount.denomination) }, { t: 'uint256', v: Helpers_1.toString(amount.ref) }, { t: 'uint256', v: Helpers_1.toString(amount.value) });
};
/**
* Given some operation hash, returns the hash of a cancel-operation message.
*/
SignedOperations.prototype.operationHashToCancelOperationHash = function (operationHash) {
var structHash = web3_1.default.utils.soliditySha3({ t: 'bytes32', v: BytesHelper_1.hashString(EIP712_CANCEL_OPERATION_STRUCT_STRING) }, { t: 'bytes32', v: BytesHelper_1.hashString('Cancel Operations') }, {
t: 'bytes32',
v: web3_1.default.utils.soliditySha3({ t: 'bytes32', v: operationHash }),
});
return this.getEIP712Hash(structHash);
};
/**
* Returns the EIP712 domain separator hash.
*/
SignedOperations.prototype.getDomainHash = function () {
return web3_1.default.utils.soliditySha3({ t: 'bytes32', v: BytesHelper_1.hashString(SignatureHelper_1.EIP712_DOMAIN_STRING) }, { t: 'bytes32', v: BytesHelper_1.hashString('SignedOperationProxy') }, { t: 'bytes32', v: BytesHelper_1.hashString('1.1') }, { t: 'uint256', v: Helpers_1.toString(this.networkId) }, {
t: 'bytes32',
v: BytesHelper_1.addressToBytes32(this.contracts.signedOperationProxy.options.address),
});
};
SignedOperations.prototype.getNetworkDomainHash = function () {
return this.contracts.signedOperationProxy.methods.EIP712_DOMAIN_HASH().call();
};
/**
* Returns a signable EIP712 Hash of a struct
*/
SignedOperations.prototype.getEIP712Hash = function (structHash) {
return web3_1.default.utils.soliditySha3({ t: 'bytes2', v: '0x1901' }, { t: 'bytes32', v: this.getDomainHash() }, { t: 'bytes32', v: structHash });
};
// ============ Private Helper Functions ============
SignedOperations.prototype.getDomainData = function () {
return {
name: 'SignedOperationProxy',
version: '1.1',
chainId: this.networkId,
verifyingContract: this.contracts.signedOperationProxy.options.address,
};
};
SignedOperations.prototype.ethSignTypedOperationInternal = function (operation, signingMethod) {
return __awaiter(this, void 0, void 0, function () {
var actionsData, operationData, data;
return __generator(this, function (_a) {
actionsData = operation.actions.map(function (action) {
return {
actionType: Helpers_1.toString(action.actionType),
accountOwner: action.primaryAccountOwner,
accountNumber: Helpers_1.toString(action.primaryAccountNumber),
assetAmount: {
sign: action.amount.sign,
denomination: Helpers_1.toString(action.amount.denomination),
ref: Helpers_1.toString(action.amount.ref),
value: Helpers_1.toString(action.amount.value),
},
primaryMarketId: Helpers_1.toString(action.primaryMarketId),
secondaryMarketId: Helpers_1.toString(action.secondaryMarketId),
otherAddress: action.otherAddress,
otherAccountOwner: action.secondaryAccountOwner,
otherAccountNumber: Helpers_1.toString(action.secondaryAccountNumber),
data: action.data,
};
});
operationData = {
actions: actionsData,
expiration: operation.expiration.toFixed(0),
salt: operation.salt.toFixed(0),
sender: operation.sender,
signer: operation.signer,
};
data = {
types: {
EIP712Domain: SignatureHelper_1.EIP712_DOMAIN_STRUCT,
Operation: EIP712_OPERATION_STRUCT,
Action: EIP712_ACTION_STRUCT,
AssetAmount: EIP712_ASSET_AMOUNT_STRUCT,
},
domain: this.getDomainData(),
primaryType: 'Operation',
message: operationData,
};
return [2 /*return*/, this.ethSignTypedDataInternal(operation.signer, data, signingMethod)];
});
});
};
SignedOperations.prototype.ethSignTypedCancelOperationInternal = function (operationHash, signer, signingMethod) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
data = {
types: {
EIP712Domain: SignatureHelper_1.EIP712_DOMAIN_STRUCT,
CancelOperation: EIP712_CANCEL_OPERATION_STRUCT,
},
domain: this.getDomainData(),
primaryType: 'CancelOperation',
message: {
action: 'Cancel Operations',
operationHashes: [operationHash],
},
};
return [2 /*return*/, this.ethSignTypedDataInternal(signer, data, signingMethod)];
});
});
};
return SignedOperations;
}(Signer_1.Signer));
exports.SignedOperations = SignedOperations;
//# sourceMappingURL=SignedOperations.js.map