Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test cases from waiting to event driven style #369

Merged
merged 31 commits into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from 29 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
85 changes: 49 additions & 36 deletions packages/tasit-action/src/contract/Action.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export class Action extends Subscription {
#signer;
#rawTx;
#tx;
#txConfirmations;
#txConfirmations = 0;
#timeout;
#lastConfirmationTime;
#isRunning = false;

constructor(rawTx, provider, signer) {
// Provider implements EventEmitter API and it's enough
Expand All @@ -33,28 +34,28 @@ export class Action extends Subscription {
};

#signAndSend = async () => {
// TODO: Go deep on gas handling.
// Without that, VM returns a revert error instead of out of gas error.
// See: https://github.com/tasitlabs/TasitSDK/issues/173
//
// This command isn't enough
// const gasLimit = await this.#provider.estimateGas(this.#rawTx);
const gasParams = {
gasLimit: 7e6,
gasPrice: 1e9,
};
try {
// TODO: Go deep on gas handling.
// Without that, VM returns a revert error instead of out of gas error.
// See: https://github.com/tasitlabs/TasitSDK/issues/173
//
// This command isn't enough
// const gasLimit = await this.#provider.estimateGas(this.#rawTx);
const gasParams = {
gasLimit: 7e6,
gasPrice: 1e9,
};

const nonce = await this.#provider.getTransactionCount(
this.#signer.address
);
const nonce = await this.#provider.getTransactionCount(
this.#signer.address
);

let rawTx = await this.#rawTx;
let rawTx = await this.#rawTx;

rawTx = { ...rawTx, nonce, ...gasParams };
rawTx = { ...rawTx, nonce, ...gasParams };

const signedTx = await this.#signer.sign(rawTx);
const signedTx = await this.#signer.sign(rawTx);

try {
this.#tx = await this.#provider.sendTransaction(signedTx);
} catch (error) {
this._emitErrorEvent(new Error(`Action with error: ${error.message}`));
Expand Down Expand Up @@ -109,7 +110,7 @@ export class Action extends Subscription {
try {
const tx = await this.#tx;
if (!tx) {
console.warn(`The action wasn't sent yet.`);
console.info(`The action wasn't sent yet.`);
return;
}

Expand All @@ -128,12 +129,11 @@ export class Action extends Subscription {
);
}

if (receipt === null) {
this.#txConfirmations = 0;
return;
}
if (!receipt) return;

const { confirmations, status } = receipt;
const txFailed = status === 0;

const txFailed = receipt.status == 0;
if (txFailed) {
this._emitErrorEventFromEventListener(
new Error(`Action failed.`),
Expand Down Expand Up @@ -161,8 +161,6 @@ export class Action extends Subscription {

this._setEventTimer(eventName, timer);

const { confirmations } = receipt;

this.#txConfirmations = confirmations;

const message = {
Expand All @@ -171,7 +169,9 @@ export class Action extends Subscription {
},
};

// Note: Unsubscribing should be done after the user's listener function is called
await listener(message);
if (once) this.off(eventName);
} catch (error) {
this._emitErrorEventFromEventListener(
new Error(`Listener function with error: ${error.message}`),
Expand All @@ -180,16 +180,29 @@ export class Action extends Subscription {
}
};

let ethersListener;

if (once) {
ethersListener = async blockNumber => {
await baseEthersListener(blockNumber);
this.off(eventName);
};
} else {
ethersListener = baseEthersListener;
}
// Note:
// On the development env (using ganache-cli)
// Blocks are being mined simultaneously and generating a sort of unexpected behaviors like:
// - once listeners called many times
// - sequential blocks giving same confirmation to a transaction
// - false-positive reorg event emission
// - collaborating for tests non-determinism
//
// Tech debt:
// See if there is another way to avoid these problems, if not
// this solution should be improved with a state structure identifying state per event
//
// Question:
// Is possible that behavior (listener concurrency calls for the same event) be desirable?
const ethersListener = async blockNumber => {
if (this.#isRunning) {
console.info(`Listener is already running`);
return;
}
this.#isRunning = true;
await baseEthersListener(blockNumber);
this.#isRunning = false;
};

this._addEventListener(eventName, ethersListener);
};
Expand Down
9 changes: 4 additions & 5 deletions packages/tasit-action/src/contract/Contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ export class Contract extends Subscription {
},
};

if (once) this.off(eventName);

// Note: Unsubscribing should be done after the user's listener function is called
await listener(message);
if (once) this.off(eventName);
} catch (error) {
this._emitErrorEventFromEventListener(
new Error(`Listener function with error: ${error.message}`),
Expand Down Expand Up @@ -177,9 +177,8 @@ export class Contract extends Subscription {

const action = new Action(rawTx, this.#provider, signer);

const errorListener = message => {
const { error } = message;
this._emitErrorEvent(new Error(`${error.message}`));
const errorListener = error => {
this._emitErrorEvent(error);
};

action.on("error", errorListener);
Expand Down
77 changes: 48 additions & 29 deletions packages/tasit-action/src/contract/Contract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe("TasitAction.Contract", () => {
});

it("and Action error event on action error", async () => {
const contractErrorListener = sinon.fake(() => {
const contractErrorListener = sinon.fake(error => {
sampleContract.off("error");
});

Expand All @@ -159,7 +159,7 @@ describe("TasitAction.Contract", () => {
// See more: https://github.com/tasitlabs/TasitSDK/issues/253
action.on("confirmation", () => {});

const actionErrorListener = sinon.fake(() => {
const actionErrorListener = sinon.fake(error => {
action.off("error");
});

Expand All @@ -175,24 +175,19 @@ describe("TasitAction.Contract", () => {
expect(actionErrorListener.callCount).to.equal(1);
});

// Non-deterministic
it.skip("on contract event listener error", async () => {
const errorListener = sinon.fake();
it("on contract event listener error", done => {
action = sampleContract.setValue("hello world");

const errorListener = sinon.fake(error => {
expect(eventListener.callCount).to.be.at.least(1);
done();
});
const eventListener = sinon.fake.throws(new Error());

sampleContract.on("error", errorListener);
sampleContract.on("ValueChanged", eventListener);

action = sampleContract.setValue("hello world");
await action.send();

await action.waitForOneConfirmation();

await mineBlocks(provider, 4);
Copy link
Member

Choose a reason for hiding this comment

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

It's nice that there are no awaits in here now


// Non-deterministic
expect(eventListener.callCount).to.be.at.least(1);
expect(errorListener.callCount).to.be.at.least(1);
action.send();
});
});

Expand Down Expand Up @@ -331,8 +326,7 @@ describe("TasitAction.Contract", () => {
action = sampleContract.setValue("hello world");
action.setEventsTimeout(100);

const errorListener = sinon.fake(message => {
const { error } = message;
const errorListener = sinon.fake(error => {
expect(error.eventName).to.equal("confirmation");
expect(error.message).to.equal("Event confirmation reached timeout.");
action.off("error");
Expand Down Expand Up @@ -421,11 +415,10 @@ describe("TasitAction.Contract", () => {
confirmationFn();
};

const errorListener = message => {
const { error } = message;

const errorListener = error => {
const { message } = error;
// Note: This assertion will not fail the test case (UnhandledPromiseRejectionWarning)
expect(error.message).to.equal(
expect(message).to.equal(
"Your action's position in the chain has changed in a surprising way."
);

Expand Down Expand Up @@ -468,11 +461,11 @@ describe("TasitAction.Contract", () => {
const confirmationListener = sinon.fake();
const errorFn = sinon.fake();

const errorListener = message => {
const { error } = message;
const errorListener = error => {
const { message } = error;

// Note: This assertion will not fail the test case (UnhandledPromiseRejectionWarning)
expect(error.message).to.equal(
expect(message).to.equal(
"Your action's position in the chain has changed in a surprising way."
);

Expand Down Expand Up @@ -541,7 +534,35 @@ describe("TasitAction.Contract", () => {

await mineBlocks(provider, 2);

expect(confirmationListener.callCount).to.equal(1);
// Non-determinitic
expect(confirmationListener.callCount).to.be.at.least(1);
expect(errorListener.called).to.be.false;
});

it("'once' listener should be unsubscribed only after user listener function was called", async () => {
action = sampleContract.setValue(rand);

const errorListener = sinon.fake(error => {
console.log(error);
action.off("error");
});

const confirmationListener = sinon.fake(message => {
console.log(message);
});

action.on("error", errorListener);
action.once("confirmation", confirmationListener);

// Forcing internal (block) listener to be called before the transaction is sent
await mineBlocks(provider, 2);

await action.send();
await action.waitForOneConfirmation();

await mineBlocks(provider, 2);

expect(confirmationListener.called).to.be.true;
expect(errorListener.called).to.be.false;
});
});
Expand All @@ -557,8 +578,7 @@ describe("TasitAction.Contract", () => {
const fakeFn = sinon.fake();
const errorFakeFn = sinon.fake();

const errorListener = message => {
const { error } = message;
const errorListener = error => {
errorFakeFn();
};

Expand Down Expand Up @@ -591,8 +611,7 @@ describe("TasitAction.Contract", () => {
const fakeFn = sinon.fake();
const errorFakeFn = sinon.fake();

const errorListener = message => {
const { error } = message;
const errorListener = error => {
errorFakeFn();
};

Expand Down
3 changes: 1 addition & 2 deletions packages/tasit-action/src/contract/Subscription.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ export class Subscription {
return;
}

const message = { error };
errorEventListener.listener(message);
errorEventListener.listener(error);
};

// TODO: Make protected
Expand Down
6 changes: 2 additions & 4 deletions packages/tasit-action/src/erc721/ERC721Full.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,7 @@ describe("TasitAction.ERC721.ERC721Full", () => {

erc721 = new ERC721Full(ERC721_FULL_ADDRESS, ana);

const contractErrorListener = errorMessage => {
const { error } = errorMessage;
const contractErrorListener = error => {
const { message } = error;
expect(message).to.equal("Action failed.");
contractErrorFakeFn();
Expand Down Expand Up @@ -240,8 +239,7 @@ describe("TasitAction.ERC721.ERC721Full", () => {

erc721 = new ERC721Full(ERC721_FULL_ADDRESS, ana);

const actionErrorListener = errorMessage => {
const { error } = errorMessage;
const actionErrorListener = error => {
const { message } = error;
expect(message).to.equal("Action failed.");
actionErrorFakeFn();
Expand Down
4 changes: 2 additions & 2 deletions packages/tasit-contract-based-account/src/GnosisSafe.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe("GnosisSafe", () => {
gnosisSafe.setWallet(gnosisSafeOwner);
const action = gnosisSafe.transferEther(toAddress, ONE);

const errorListener = async message => {
const errorListener = async error => {
onError();
action.unsubscribe();
};
Expand Down Expand Up @@ -184,7 +184,7 @@ describe("GnosisSafe", () => {
gnosisSafe.setWallet(ephemeralAccount);
const action = erc20.transferFrom(GNOSIS_SAFE_ADDRESS, toAddress, ONE);

const errorListener = async message => {
const errorListener = async error => {
onError();
action.unsubscribe();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ const createParcel = async parcel => {
resolve(id);
});

action.on("error", message => {
const { error } = message;
console.log(error.message);
action.on("error", error => {
const { message } = error;
console.log(message);
action.unsubscribe();
reject();
});
Expand Down Expand Up @@ -252,16 +252,16 @@ const createEstate = async estate => {
// See more: https://github.com/tasitlabs/TasitSDK/issues/253
action.on("confirmation", message => {});

action.on("error", message => {
const { error } = message;
console.log(error.message);
action.on("error", error => {
const { message } = error;
console.log(message);
action.unsubscribe();
reject();
});

estateContract.on("error", message => {
const { error } = message;
console.log(error.message);
estateContract.on("error", error => {
const { message } = error;
console.log(message);
estateContract.unsubscribe();
reject();
});
Expand Down
Loading