Skip to content
This repository was archived by the owner on Jan 18, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions contracts/core/extensions/CoreIssuanceOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ contract CoreIssuanceOrder is
/* ============ Constants ============ */

uint256 constant HEADER_LENGTH = 64;

string constant INVALID_EXCHANGE = "Exchange does not exist.";
string constant INVALID_CANCEL_ORDER = "Only maker can cancel order.";
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.";
Expand Down Expand Up @@ -94,7 +95,7 @@ contract CoreIssuanceOrder is
)
});

// Verify order is valid
// Verify order is valid and return amount to be filled
validateOrder(
order,
_fillQuantity
Expand All @@ -115,6 +116,10 @@ contract CoreIssuanceOrder is
// Execute exchange orders
executeExchangeOrders(_orderData);

// TO DO: When openOrder amount functionality added these must change
// Tally fill in orderFills mapping
state.orderFills[order.orderHash] = state.orderFills[order.orderHash].add(_fillQuantity);

//Issue Set
issueInternal(
order.makerAddress,
Expand All @@ -123,6 +128,51 @@ contract CoreIssuanceOrder is
);
}

/**
* Cancel an issuance order
*
* @param _addresses [setAddress, makerAddress, makerToken, relayerToken]
* @param _values [quantity, makerTokenAmount, expiration, relayerTokenAmount, salt]
* @param _cancelQuantity Quantity of set to be filled
*/
function cancelOrder(
address[4] _addresses,
uint[5] _values,
uint _cancelQuantity
)
external
isPositiveQuantity(_cancelQuantity)
{
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
)
});

// Make sure cancel order comes from maker
require(order.makerAddress == msg.sender, INVALID_CANCEL_ORDER);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also check that the order hasn't been already cancelled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah my original comment addresses that...it's gonna be in the next PR.

// Verify order is valid and return amount to be cancelled
validateOrder(
order,
_cancelQuantity
);

// TO DO: When openOrder amount functionality added these must change
// Tally cancel in orderCancels mapping
state.orderCancels[order.orderHash] = state.orderCancels[order.orderHash].add(_cancelQuantity);
}

/* ============ Private Functions ============ */

/**
Expand Down
16 changes: 16 additions & 0 deletions contracts/core/lib/CoreState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,20 @@ contract CoreState {
{
return state.validSets[_set];
}

function orderFills(bytes32 _orderHash)
public
view
returns(uint)
{
return state.orderFills[_orderHash];
}

function orderCancels(bytes32 _orderHash)
public
view
returns(uint)
{
return state.orderCancels[_orderHash];
}
}
118 changes: 118 additions & 0 deletions test/core/extensions/coreIssuanceOrder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ contract("CoreIssuanceOrder", (accounts) => {
assertTokenBalance(setToken, existingBalance.add(subjectQuantityToIssue), signerAddress);
});

it("marks the correct amount as filled in orderFills mapping", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure your test pulls the value before (even if it's 0), and checks that the ending value is what you intended. See: coreIssuance.spec.ts#transfers the required tokens from the user

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool

const preFilled = await core.orderFills.callAsync(issuanceOrderParams.orderHash);
expect(preFilled).to.be.bignumber.equal(ZERO);

await subject();

const filled = await core.orderFills.callAsync(issuanceOrderParams.orderHash);
expect(filled).to.be.bignumber.equal(subjectQuantityToIssue);
});

describe("when the quantity to issue is not positive", async () => {
beforeEach(async () => {
subjectQuantityToIssue = ZERO;
Expand Down Expand Up @@ -236,4 +246,112 @@ contract("CoreIssuanceOrder", (accounts) => {
});
});
});

describe("#cancelOrder", async () => {
let subjectCaller: Address;
let subjectQuantityToCancel: BigNumber;
let subjectExchangeOrdersData: Bytes32;

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 () => {
signerAddress = signerAccount;

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,
setTokenFactory.address,
componentAddresses,
componentUnits,
naturalUnit,
);

await coreWrapper.registerDefaultExchanges(core);

subjectCaller = signerAccount;
subjectQuantityToCancel = ether(2);
issuanceOrderParams = await generateFillOrderParameters(setToken.address, signerAddress, signerAddress, componentAddresses[0]);
subjectExchangeOrdersData = generateOrdersDataForOrderCount(3);
});

async function subject(): Promise<string> {
return core.cancelOrder.sendTransactionAsync(
issuanceOrderParams.addresses,
issuanceOrderParams.values,
subjectQuantityToCancel,
{ from: subjectCaller },
);
}

it("marks the correct amount as canceled in orderCancels mapping", async () => {
const preCanceled = await core.orderCancels.callAsync(issuanceOrderParams.orderHash);
expect(preCanceled).to.be.bignumber.equal(ZERO);

await subject();

const canceled = await core.orderCancels.callAsync(issuanceOrderParams.orderHash);
expect(canceled).to.be.bignumber.equal(subjectQuantityToCancel);
});

describe("when the quantity to cancel is not positive", async () => {
beforeEach(async () => {
subjectQuantityToCancel = ZERO;
});

it("should revert", async () => {
await expectRevertError(subject());
});
});

describe("when the transaction sender is not the maker", async () => {
beforeEach(async () => {
subjectCaller = takerAccount;
});

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());
});
});
});
});