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

fix: force RefundableCrowdsale to also be a PostDeliveryCrowdsale #927

Closed

Conversation

shrugs
Copy link
Contributor

@shrugs shrugs commented May 4, 2018

Fixes #877

🚀 Description

When participating in a refundable crowdsale, because tokens were delivered immediately, malicious investors could previously sell tokens to unsuspecting users and then collect their own ether-based refund after the goal is not met.

This change forces RefundableCrowdsale to also be a PostDeliveryCrowdsale where users are only allowed to withdraw tokens if the goal is reached, the crowdsale is over, and it is finalized.

  • 📘 I've reviewed the OpenZeppelin Contributor Guidelines
  • ✅ I've added tests where applicable to test my new functionality.
  • 📖 I've made sure that my contracts are well-documented.
  • 🎨 I've run the JS/Solidity linters and fixed any issues (npm run lint:all:fix).

@shrugs shrugs added the review label May 4, 2018
@shrugs shrugs self-assigned this May 4, 2018
@shrugs shrugs requested a review from frangio May 4, 2018 01:02
@shrugs
Copy link
Contributor Author

shrugs commented May 4, 2018

Any reason why ci is reporting failure? logs look happy

@frangio
Copy link
Contributor

frangio commented May 4, 2018

It's the linting:

examples/SampleCrowdsale.test.js
  69:11  error  'expectedTokenAmount' is assigned a value but never used  no-unused-vars

I've been confused by this before. Maybe linting should run as a separate job?

@shrugs
Copy link
Contributor Author

shrugs commented May 4, 2018

nice, thanks; looks like I didn't actually run that link command like the checkbox says I did 😅

Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

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

The smart contracts look good. I'm not convinced by the way the PostDelivery behavior was implemented, parameterizing by the endCrowdsale procedure.

I left some concrete ideas.

const rate = new BigNumber(1);
const goal = ether(50);
const lessThanGoal = ether(45);
const tokenSupply = new BigNumber('1e22');

before(async function () {
beforeEach(async function () {
// Advance to the next block to correctly read time in the solidity "now" function interpreted by ganache
await advanceBlock();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I prefer having this in a before block as it was before if that's all that's strictly necessary.

Actually, can you check if removing this line makes the tests fail? I feel like it was a Ganache bug that may have been fixed by now.


context('after end', function () {
beforeEach(async function () {
await endCrowdsale.call(this, investor, purchaser, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

There are a couple issues here.

endCrowdsale is actually a function with no arguments which wraps each test's this.endCrowdsale. So, these parameters (investor, etc.) are never used. I think it's reasonable to expect the endCrowdsale function to not need the parameters because the behavior itself receives them from there.

I don't think it's necessary or okay to receive endCrowdsale as a behavior argument; the behavior should just access this.endCrowdsale directly. Refundable's has a boolean parameter but it should have an appropriate default value so that this works, which should be true in this case.

await increaseTimeTo(this.openingTime);
await this.crowdsale.buyTokens(investor, { value, from: purchaser });
await this.crowdsale.buyTokens(otherInvestor, {
value: goalReached ? goal.minus(value) : lessThanGoal,
Copy link
Contributor

Choose a reason for hiding this comment

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

lessThanGoal was meant to be used "by itself", not together with another investment of value, so it's kind of a coincidence that value + lessThanGoal < goal.

Maybe don't do this second investment at all if goalReached == true?

};
});

shouldBePostDeliveryCrowdsale(investor, purchaser, value, async function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about removing this.endCrowdsale altogether and using goal instead of value here? I think this communicates the idea that: this should behave exactly like a PostDeliveryCrowdsale as long as the money invested is at least goal. It's not ideal because it wouldn't be testing a scenario where the goal is reached by more than one investment. But I'm not convinced by this.endCrowdsale.

@vittominacori
Copy link
Contributor

@frangio @shrugs
You are forcing investors to make another transaction after the crowdsale end to claim their tokens.

If the issue is that users can transfer their tokens before the end, is it not better to force using an ERC20 token with a RBAC on who is enabled to transfer?

For instance adding a transferEnabled boolean to the token and add a specific role for transfer events.
Then add the canTransfer modifier to transfer and transferFrom.

modifier canTransfer() {
    require(transferEnabled || hasRole(msg.sender, ROLE_TRANSFER));
    _;
  }

I can build this token.
What do you think about?

@shrugs
Copy link
Contributor Author

shrugs commented May 24, 2018

That's another fine approach. The difference is that with a post delivery crowdsale the logic is in the crowdsale and not the token contract. @frangio which approach do you like more?

@vittominacori
Copy link
Contributor

My concern with forcing a post delivery is that investors should be able to make a transaction to the withdrawTokensfunction.

If buyTokens could be simple like sending an amount of ETH, to withdraw their tokens, instead, they should be able to copy the ABI, use MEW... or the crowdsale builder should prepare a DApp where they can withdraw tokens with MetaMask (and investors should have invested using MetaMask or import private keys into).

So, if it is not strictly necessary (as developer and I think also as business developer), I would prefer to build the logic when I build the token and not to force people to understand how to use ABIs.

@nventuro nventuro removed the review label Aug 24, 2018
@frangio frangio added this to the v2.0 milestone Sep 26, 2018
@come-maiz come-maiz removed this from the v2.0 milestone Oct 18, 2018
@nventuro
Copy link
Contributor

nventuro commented Dec 6, 2018

Closing in favor of #1543: this PR got stale, and it contains lots of unrelated changes (e.g. code formatting, SampleCrowdsale tests, etc.).

Note that the actual fix is the same as in #1543 though.

@nventuro nventuro closed this Dec 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refundable Crowdsale
5 participants