diff --git a/contracts/core/extensions/CoreIssuance.sol b/contracts/core/extensions/CoreIssuance.sol index 3c2977a50..eb587465a 100644 --- a/contracts/core/extensions/CoreIssuance.sol +++ b/contracts/core/extensions/CoreIssuance.sol @@ -141,7 +141,7 @@ contract CoreIssuance is /** * Exchanges components for Set Tokens, accepting any owner * - * @param _owner Address to issue set to + * @param _owner Address to issue set to * @param _setAddress Address of set to issue * @param _quantity Quantity of set to issue */ diff --git a/contracts/core/extensions/CoreIssuanceOrder.sol b/contracts/core/extensions/CoreIssuanceOrder.sol index 22c622a3c..bc5e5e06c 100644 --- a/contracts/core/extensions/CoreIssuanceOrder.sol +++ b/contracts/core/extensions/CoreIssuanceOrder.sol @@ -18,8 +18,9 @@ pragma solidity 0.4.24; import { SafeMath } from "zeppelin-solidity/contracts/math/SafeMath.sol"; -import { ICoreIssuance } from "../interfaces/ICoreIssuance.sol"; import { CoreModifiers } from "../lib/CoreSharedModifiers.sol"; +import { ICoreIssuance } from "../interfaces/ICoreIssuance.sol"; +import { OrderLibrary } from "../lib/OrderLibrary.sol"; /** @@ -36,17 +37,106 @@ contract CoreIssuanceOrder is { using SafeMath for uint256; + /* ============ Constants ============ */ + + string constant INVALID_SIGNATURE = "Invalid order signature."; + string constant INVALID_TOKEN_AMOUNTS = "Quantity and makerTokenAmount should be greater than 0."; + string constant ORDER_EXPIRED = "This order has expired."; + + /* ============ External Functions ============ */ + + /** + * Fill an issuance order + * + * @param _addresses [setAddress, makerAddress, makerToken, relayerToken] + * @param _values [quantity, makerTokenAmount, expiration, relayerTokenAmount, salt] + * @param _fillQuantity Quantity of set to be filled + * @param _v v element of ECDSA signature + * @param _r r element of ECDSA signature + * @param _s s element of ECDSA signature + */ function fillOrder( - address _maker, - address _setAddress, - uint _quantity + address[4] _addresses, + uint[5] _values, + uint _fillQuantity, + uint8 _v, + bytes32 _r, + bytes32 _s ) - public - isValidSet(_setAddress) - isPositiveQuantity(_quantity) - isNaturalUnitMultiple(_quantity, _setAddress) + external + isValidSet(_addresses[0]) + isPositiveQuantity(_fillQuantity) + isNaturalUnitMultiple(_fillQuantity, _addresses[0]) { + + OrderLibrary.IssuanceOrder memory order = OrderLibrary.IssuanceOrder({ + setAddress: _addresses[0], + quantity: _values[0], + makerAddress: _addresses[1], + makerToken: _addresses[2], + makerTokenAmount: _values[1], + expiration: _values[2], + relayerToken: _addresses[3], + relayerTokenAmount: _values[3], + salt: _values[4], + orderHash: OrderLibrary.generateOrderHash( + _addresses, + _values + ) + }); + + // Verify order is valid + validateOrder( + order, + _fillQuantity + ); + + // Verify signature is authentic + require( + OrderLibrary.validateSignature( + order.orderHash, + order.makerAddress, + _v, + _r, + _s + ), + INVALID_SIGNATURE + ); + //Issue Set - issueInternal(_maker, _setAddress, _quantity); + issueInternal( + order.makerAddress, + order.setAddress, + _fillQuantity + ); + } + + /* ============ Internal Functions ============ */ + + /** + * Validate order params are still valid + * + * @param _order IssuanceOrder object containing order params + * @param _fillQuantity Quantity of Set to be filled + */ + function validateOrder( + OrderLibrary.IssuanceOrder _order, + uint _fillQuantity + ) + internal + view + { + // Make sure makerTokenAmount and Set Token to issue is greater than 0. + require( + _order.makerTokenAmount > 0 && _order.quantity > 0, + INVALID_TOKEN_AMOUNTS + ); + // Make sure the order hasn't expired + require( + block.timestamp <= _order.expiration, + ORDER_EXPIRED + ); + // TO DO: Check to make sure quantity is multiple of natural unit + // TO DO: Check to see if filled } -} \ No newline at end of file +} diff --git a/contracts/core/lib/OrderLibrary.sol b/contracts/core/lib/OrderLibrary.sol new file mode 100644 index 000000000..d08723e32 --- /dev/null +++ b/contracts/core/lib/OrderLibrary.sol @@ -0,0 +1,114 @@ +/* + Copyright 2018 Set Labs 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.4.24; + + +/** + * @title OrderLibrary + * @author Set Protocol + * + * The Order Library contains functions for checking validation and hashing of Orders. + * + */ + +library OrderLibrary { + + /* ============ Structs ============ */ + + struct IssuanceOrder { + address setAddress; // _addresses[0] + uint256 quantity; // _values[0] + address makerAddress; // _addresses[1] + address makerToken; // _addresses[2] + uint256 makerTokenAmount; // _values[1] + uint256 expiration; // _values[2] + address relayerToken; // _addresses[3] + uint256 relayerTokenAmount; // _values[3] + uint256 salt; // _values[4] + bytes32 orderHash; + } + + /* ============ Internal Functions ============ */ + + /** + * Create hash of order parameters + * + * @param _addresses [setAddress, makerAddress, makerToken, relayerToken] + * @param _values [quantity, makerTokenAmount, expiration, relayerTokenAmount, salt] + */ + function generateOrderHash( + address[4] _addresses, + uint[5] _values + ) + internal + pure + returns(bytes32) + { + // Hash the order parameters + return keccak256( + abi.encodePacked( + _addresses[0], // setAddress + _addresses[1], // makerAddress + _addresses[2], // makerToken + _addresses[3], // relayerToken + _values[0], // quantity + _values[1], // makerTokenAmount + _values[2], // expiration + _values[3], // relayerTokenAmount + _values[4] // salt + ) + ); + } + + /** + * Validate order signature + * + * @param _orderHash Hash of issuance order + * @param _signerAddress Address of Issuance Order signer + * @param _v v element of ECDSA signature + * @param _r r element of ECDSA signature + * @param _s s element of ECDSA signature + */ + function validateSignature( + bytes32 _orderHash, + address _signerAddress, + uint8 _v, + bytes32 _r, + bytes32 _s + ) + internal + pure + returns(bool) + { + // Public address returned by ecrecover function + address recAddress; + + // Ethereum msg prefix + bytes memory msgPrefix = "\x19Ethereum Signed Message:\n32"; + + // Find what address signed the order + recAddress = ecrecover( + keccak256(abi.encodePacked(msgPrefix, _orderHash)), + _v, + _r, + _s + ); + + return recAddress == _signerAddress; + } + +} diff --git a/contracts/test/lib/MockOrderLibrary.sol b/contracts/test/lib/MockOrderLibrary.sol new file mode 100644 index 000000000..e4aa74c40 --- /dev/null +++ b/contracts/test/lib/MockOrderLibrary.sol @@ -0,0 +1,42 @@ +pragma solidity 0.4.24; +pragma experimental "ABIEncoderV2"; + +import { OrderLibrary } from "../../core/lib/OrderLibrary.sol"; + +// Mock contract implementation of OrderLibrary functions +contract MockOrderLibrary { + function testGenerateOrderHash( + address[4] _addresses, + uint[5] _values + ) + public + pure + returns(bytes32) + { + return OrderLibrary.generateOrderHash( + _addresses, + _values + ); + } + + function testValidateSignature( + bytes32 _orderHash, + address _signerAddress, + uint8 _v, + bytes32 _r, + bytes32 _s + ) + public + pure + returns(bool) + { + return OrderLibrary.validateSignature( + _orderHash, + _signerAddress, + _v, + _r, + _s + ); + } +} + diff --git a/package.json b/package.json index 047aa0c23..0d97ae794 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "dist": "bash scripts/prepare_dist.sh", "clean": "rm -rf build; rm -rf transpiled; rm -rf types/generated", "compile": "truffle compile", - "prepare-test": "yarn clean && truffle compile --all && yarn run generate-typings && yarn run transpile", + "prepare-test": "yarn clean && truffle compile --all && yarn run generate-typings && yarn run transpile", "test": "yarn prepare-test && truffle test `find ./transpiled/test -name '*.spec.js'`", "transpile": "tsc", "generate-typings": "abi-gen --abis './build/contracts/*.json' --out './types/generated' --template './types/contract_templates/contract.mustache' --partials './types/contract_templates/partials/*.mustache' && yarn run rename-generated-abi", diff --git a/test/core/extensions/coreIssuanceOrder.spec.ts b/test/core/extensions/coreIssuanceOrder.spec.ts index c44cc5d73..b44e7c120 100644 --- a/test/core/extensions/coreIssuanceOrder.spec.ts +++ b/test/core/extensions/coreIssuanceOrder.spec.ts @@ -6,7 +6,7 @@ import { BigNumber } from "bignumber.js"; import { ether } from "../../utils/units"; // Types -import { Address } from "../../../types/common.js"; +import { Address, IssuanceOrder } from "../../../types/common.js"; // Contract types import { CoreContract } from "../../../types/generated/core"; @@ -22,6 +22,9 @@ const Core = artifacts.require("Core"); // Core wrapper import { CoreWrapper } from "../../utils/coreWrapper"; import { ERC20Wrapper } from "../../utils/erc20Wrapper"; +import { + generateFillOrderParameters, +} from "../../utils/orderWrapper"; // Testing Set up import { BigNumberSetup } from "../../config/bigNumberSetup"; @@ -41,13 +44,18 @@ import { import { DEPLOYED_TOKEN_QUANTITY, + ZERO, + NULL_ADDRESS, } from "../../utils/constants"; -contract("CoreIssuance", (accounts) => { +contract("CoreIssuanceOrder", (accounts) => { const [ ownerAccount, takerAccount, makerAccount, + signerAccount, + mockSetTokenAccount, + mockTokenAccount ] = accounts; let core: CoreContract; @@ -77,18 +85,23 @@ contract("CoreIssuance", (accounts) => { describe("#fillOrder", async () => { let subjectCaller: Address; let subjectQuantityToIssue: BigNumber; - let subjectSetToIssue: Address; const naturalUnit: BigNumber = ether(2); let components: StandardTokenMockContract[] = []; let componentUnits: BigNumber[]; let setToken: SetTokenContract; + let signerAddress: Address; + let componentAddresses: Address[]; + + let issuanceOrderParams: any; beforeEach(async () => { - components = await erc20Wrapper.deployTokensAsync(2, makerAccount); - await erc20Wrapper.approveTransfersAsync(components, transferProxy.address, makerAccount); + signerAddress = signerAccount; - const componentAddresses = _.map(components, (token) => token.address); + components = await erc20Wrapper.deployTokensAsync(2, signerAddress); //For current purposes issue to maker/signer + await erc20Wrapper.approveTransfersAsync(components, transferProxy.address, signerAddress); + + componentAddresses = _.map(components, (token) => token.address); componentUnits = _.map(components, () => ether(4)); // Multiple of naturalUnit setToken = await coreWrapper.createSetTokenAsync( core, @@ -100,14 +113,18 @@ contract("CoreIssuance", (accounts) => { subjectCaller = takerAccount; subjectQuantityToIssue = ether(2); - subjectSetToIssue = setToken.address; + + issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, signerAddress, componentAddresses[0]); }); async function subject(): Promise { return core.fillOrder.sendTransactionAsync( - makerAccount, - subjectSetToIssue, + issuanceOrderParams.addresses, + issuanceOrderParams.values, subjectQuantityToIssue, + issuanceOrderParams.signature.v, + issuanceOrderParams.signature.r, + issuanceOrderParams.signature.s, { from: subjectCaller }, ); } @@ -116,14 +133,83 @@ contract("CoreIssuance", (accounts) => { const component: StandardTokenMockContract = _.first(components); const unit: BigNumber = _.first(componentUnits); - const existingBalance = await component.balanceOf.callAsync(makerAccount); - assertTokenBalance(component, DEPLOYED_TOKEN_QUANTITY, makerAccount); - + const existingBalance = await component.balanceOf.callAsync(signerAddress); + assertTokenBalance(component, DEPLOYED_TOKEN_QUANTITY, signerAddress); await subject(); - const newBalance = await component.balanceOf.callAsync(makerAccount); + const newBalance = await component.balanceOf.callAsync(signerAddress); const expectedNewBalance = existingBalance.sub(subjectQuantityToIssue.div(naturalUnit).mul(unit)); expect(newBalance).to.be.bignumber.equal(expectedNewBalance); }); + it("mints the correct quantity of the set for the user", async () => { + const existingBalance = await setToken.balanceOf.callAsync(signerAddress); + + await subject(); + + assertTokenBalance(setToken, existingBalance.add(subjectQuantityToIssue), signerAddress); + }); + describe("when the quantity to issue is not positive", async () => { + beforeEach(async () => { + subjectQuantityToIssue = ZERO; + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when the set was not created through core", async () => { + beforeEach(async () => { + issuanceOrderParams = await generateFillOrderParameters(NULL_ADDRESS, signerAddress, signerAddress, componentAddresses[0]) + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when the quantity is not a multiple of the natural unit of the set", async () => { + beforeEach(async () => { + subjectQuantityToIssue = ether(3); + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when the order has expired", async () => { + beforeEach(async () => { + issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, signerAddress, componentAddresses[0], undefined, undefined, -1) + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when invalid Set Token quantity in Issuance Order", async () => { + beforeEach(async () => { + issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, signerAddress, componentAddresses[0], ZERO) + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when invalid makerTokenAmount in Issuance Order", async () => { + beforeEach(async () => { + issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, signerAddress, componentAddresses[0], undefined, ZERO) + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); + describe("when the message is not signed by the maker", async () => { + beforeEach(async () => { + issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, makerAccount, componentAddresses[0]) + }); + + it("should revert", async () => { + await expectRevertError(subject()); + }); + }); }); }); diff --git a/test/core/lib/orderLibrary.spec.ts b/test/core/lib/orderLibrary.spec.ts new file mode 100644 index 000000000..158373c0c --- /dev/null +++ b/test/core/lib/orderLibrary.spec.ts @@ -0,0 +1,126 @@ +import * as chai from "chai"; +import * as _ from "lodash"; + +import * as ABIDecoder from "abi-decoder"; +import { BigNumber } from "bignumber.js"; +import { ether } from "../../utils/units"; + +// Types +import { Address, IssuanceOrder } from "../../../types/common.js"; + +// Contract types +import { MockOrderLibraryContract } from "../../../types/generated/mock_order_library"; + +// Artifacts +const MockOrderLibrary = artifacts.require("MockOrderLibrary"); + +// Core wrapper +import { CoreWrapper } from "../../utils/coreWrapper"; +import { + generateFillOrderParameters, +} from "../../utils/orderWrapper"; + +// Testing Set up +import { BigNumberSetup } from "../../config/bigNumberSetup"; +import ChaiSetup from "../../config/chaiSetup"; +BigNumberSetup.configure(); +ChaiSetup.configure(); +const { expect, assert } = chai; + +import { + expectRevertError, +} from "../../utils/tokenAssertions"; + +import { + ZERO, + NULL_ADDRESS, +} from "../../utils/constants"; + +contract("OrderLibrary", (accounts) => { + const [ + ownerAccount, + takerAccount, + makerAccount, + signerAccount, + mockSetTokenAccount, + mockTokenAccount + ] = accounts; + + let orderLib: MockOrderLibraryContract; + + const coreWrapper = new CoreWrapper(ownerAccount, ownerAccount); + + beforeEach(async () => { + orderLib = await coreWrapper.deployMockOrderLibAsync(); + }); + + describe("#validateSignature", async () => { + let subjectCaller: Address; + let subjectMaker: Address; + let signerAddress: Address; + + let issuanceOrderParams: any; + + beforeEach(async () => { + + subjectCaller = takerAccount; + subjectMaker = signerAccount; + signerAddress = signerAccount; + + issuanceOrderParams = await generateFillOrderParameters(mockSetTokenAccount, signerAddress, signerAddress, mockTokenAccount); + }); + + async function subject(): Promise { + return orderLib.testValidateSignature.callAsync( + issuanceOrderParams.orderHash, + subjectMaker, + issuanceOrderParams.signature.v, + issuanceOrderParams.signature.r, + issuanceOrderParams.signature.s, + { from: subjectCaller }, + ); + } + + it("should return true", async () => { + const validSig = await subject(); + + expect(validSig).to.equal(true); + }); + describe("when the message is not signed by the maker", async () => { + beforeEach(async () => { + subjectMaker = makerAccount; + }); + + it("should return false", async () => { + const validSig = await subject(); + + expect(validSig).to.equal(false); + }); + }); + }); + describe("#generateOrderHash", async () => { + let subjectCaller: Address; + + let issuanceOrderParams: any; + + beforeEach(async () => { + subjectCaller = takerAccount; + + issuanceOrderParams = await generateFillOrderParameters(mockSetTokenAccount, makerAccount, makerAccount, mockTokenAccount); + }); + + async function subject(): Promise { + return orderLib.testGenerateOrderHash.callAsync( + issuanceOrderParams.addresses, + issuanceOrderParams.values, + { from: subjectCaller }, + ); + } + + it("should return true", async () => { + const contractOrderHash = await subject(); + + expect(contractOrderHash).to.equal(issuanceOrderParams.orderHash); + }); + }); +}); \ No newline at end of file diff --git a/test/utils/constants.ts b/test/utils/constants.ts index be7b19e17..a06bb6149 100644 --- a/test/utils/constants.ts +++ b/test/utils/constants.ts @@ -11,6 +11,7 @@ export const STANDARD_QUANTITY_ISSUED: BigNumber = ether(10); export const STANDARD_NATURAL_UNIT = ether(1); export const STANDARD_COMPONENT_UNIT = ether(1); export const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); +export const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export const ZERO: BigNumber = new BigNumber(0); export const ONE: BigNumber = new BigNumber(1); @@ -20,3 +21,21 @@ export const ORDER_TYPE = { KYBER: "kyber", MAKER_WALLET: "makerWallet", } + +export const PRIVATE_KEYS = [ + "767df558efc63b6ba9a9257e68509c38f5c48d5938a41ab191a9a073ebff7c4f", + "6dc5d3331608e4b99b68dd8b9dee89973ebc6feee1cb0ba9a2ec814a99c680c1", + "2b73a8e22d40615e0144bee14c5f80668c449029d9f60eba71b800f0979337af", + "95fab20a86f7aa81c47854e3a0d265014359d557027c3e07c64dbac9f66b0930", + "b8400424887469943ca6e4448596a709c64ad768600e9c24538fda6c8f5d1599", + "737392faafc4b9fa8861ec0dfb1e3a01e383e270331faa25f28fb40866740046", + "92c0c7a1aba1e59f1f97af9a482649a4196b2b766f794b85bebcefeac8b80e30", + "05973025898f0c1c1c723545630ecd251c42297d8db665aec245526022d82a98", + "84f5e4921ea1ee2e53e2713bd5170877635c168d0fdab8044f15b835020f1f6c", + "b17ee27cba4f656d8cd13165ba9fdfa79abf5308253c75654030d360f1e65329", + "55d6cb34052149b6a93624bbfd1a277a37e352f34e16bc405054adf42b6eab18", + "cc87c4b4d1665092048511f0318755884771d498f79d29aa78b341a3f058f4c6", + "8884500103a7809924cbb5b6e7c1f8b9e27e7b6da839935f221406e02a954293", + "0a44845c2b09e9f942578f7dd960653595c152e558dbf7fb40bd85e918dd565f", + "843445407853ed9455b0b3511b50dc11a5c329746abbed08c95582b895c450a9", +] diff --git a/test/utils/coreWrapper.ts b/test/utils/coreWrapper.ts index 275f8484c..0306f7f6e 100644 --- a/test/utils/coreWrapper.ts +++ b/test/utils/coreWrapper.ts @@ -2,6 +2,7 @@ import * as _ from "lodash"; import { AuthorizableContract } from "../../types/generated/authorizable"; import { CoreContract } from "../../types/generated/core"; +import { MockOrderLibraryContract } from "../../types/generated/mock_order_library"; import { SetTokenContract } from "../../types/generated/set_token"; import { SetTokenFactoryContract } from "../../types/generated/set_token_factory"; import { StandardTokenMockContract } from "../../types/generated/standard_token_mock"; @@ -16,6 +17,7 @@ import { extractNewSetTokenAddressFromLogs } from "../logs/contracts/core"; const Authorizable = artifacts.require("Authorizable"); const Core = artifacts.require("Core"); +const MockOrderLibrary = artifacts.require("MockOrderLibrary"); const TransferProxy = artifacts.require("TransferProxy"); const SetTokenFactory = artifacts.require("SetTokenFactory"); const Vault = artifacts.require("Vault"); @@ -92,6 +94,19 @@ export class CoreWrapper { ); } + public async deployMockOrderLibAsync( + from: Address = this._tokenOwnerAddress + ): Promise { + const truffleMockOrderLibrary = await MockOrderLibrary.new( + { from }, + ); + + return new MockOrderLibraryContract( + web3.eth.contract(truffleMockOrderLibrary.abi).at(truffleMockOrderLibrary.address), + { from, gas: DEFAULT_GAS }, + ); + } + public async deploySetTokenAsync( factory: Address, componentAddresses: Address[], diff --git a/test/utils/orderWrapper.ts b/test/utils/orderWrapper.ts index 3a9952b72..9277934d9 100644 --- a/test/utils/orderWrapper.ts +++ b/test/utils/orderWrapper.ts @@ -1,12 +1,33 @@ import * as _ from "lodash"; import * as ethUtil from "ethereumjs-util"; +import * as ethABI from 'ethereumjs-abi'; import { BigNumber } from "bignumber.js"; -import { Address, Bytes32, UInt } from "../../types/common.js"; +import BN = require('bn.js'); + +import { Address, Bytes32, UInt, IssuanceOrder, SolidityTypes } from "../../types/common.js"; import { ORDER_TYPE, + MAX_DIGITS_IN_UNSIGNED_256_INT, } from "../utils/constants"; +import { ether } from "./units"; + +function bigNumberToBN(value: BigNumber) { + return new BN(value.toString(), 10); +} + +function parseSigHexAsRSV(sigHex: string): any { + const { v,r,s } = ethUtil.fromRpcSig(sigHex); + + const ecSig = { + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }; + return ecSig +} + export function generateExchangeOrdersHeader( orderCount: UInt ): Bytes32 { @@ -47,3 +68,86 @@ export function generateExchangeOrdersData( return ethUtil.bufferToHex(buffer); } + +export function generateSalt(): BigNumber { + const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT-1); + const salt = randomNumber.times(factor).round(); + return salt; +} + +export function generateTimeStamp( + min: number, +): BigNumber { + const timeToExpiration = min * 60 * 1000; + const expiration = new BigNumber(Math.floor((Date.now() + timeToExpiration)/1000)); + return expiration; +} + +export function hashOrderHex( + order: IssuanceOrder, +): string { + const orderParts = [ + {value: order.setAddress, type: SolidityTypes.Address}, + {value: order.makerAddress, type: SolidityTypes.Address}, + {value: order.makerToken, type: SolidityTypes.Address}, + {value: order.relayerToken, type: SolidityTypes.Address}, + {value: bigNumberToBN(order.quantity), type: SolidityTypes.Uint256}, + {value: bigNumberToBN(order.makerTokenAmount), type: SolidityTypes.Uint256}, + {value: bigNumberToBN(order.expiration), type: SolidityTypes.Uint256}, + {value: bigNumberToBN(order.relayerTokenAmount), type: SolidityTypes.Uint256}, + {value: bigNumberToBN(order.salt), type: SolidityTypes.Uint256} + ] + + const types = _.map(orderParts, o => o.type); + const values = _.map(orderParts, o => o.value); + const hashBuff = ethABI.soliditySHA3(types, values); + const hashHex = ethUtil.bufferToHex(hashBuff); + return hashHex; +} + +export async function signMessage( + msg: string, + address: Address +): Promise { + const normalSigner = String(address).toLowerCase(); + + const sig = web3.eth.sign(address, msg); + const ecSig = parseSigHexAsRSV(sig); + return ecSig; +} + +export async function generateFillOrderParameters( + setAddress: Address, + signerAddress: Address, + makerAddress: Address, + componentAddress: Address, + quantity: BigNumber = ether(4), + makerTokenAmount: BigNumber = ether(10), + timeToExpiration: number = 10, + +): Promise { + const order = { + setAddress, + quantity, + makerAddress, + makerToken: componentAddress, + makerTokenAmount, + expiration: generateTimeStamp(timeToExpiration), + relayerToken: componentAddress, + relayerTokenAmount: ether(1), + salt: generateSalt() + } as IssuanceOrder; + + const addresses = [order.setAddress, order.makerAddress, order.makerToken, order.relayerToken]; + const values = [order.quantity, order.makerTokenAmount, order.expiration, order.relayerTokenAmount, order.salt]; + + const orderHash = hashOrderHex(order); + const signature = await signMessage(orderHash, signerAddress); + return { + addresses, + values, + orderHash, + signature, + }; +} diff --git a/types/common.ts b/types/common.ts index 27c6d42c6..b01c8ab01 100644 --- a/types/common.ts +++ b/types/common.ts @@ -29,6 +29,18 @@ export interface TxDataPayable extends TxData { value?: BigNumber; } +export interface IssuanceOrder { + setAddress: Address, + quantity: BigNumber, + makerAddress: Address, + makerToken: Address, + makerTokenAmount: BigNumber, + expiration: BigNumber, + relayerToken: Address, + relayerTokenAmount: BigNumber, + salt: BigNumber +} + export interface Log { event: string; address: Address; @@ -38,3 +50,10 @@ export interface Log { export type Address = string; export type UInt = number | BigNumber; export type Bytes32 = string; + +export enum SolidityTypes { + Address = 'address', + Uint256 = 'uint256', + Uint8 = 'uint8', + Uint = 'uint', +}