-
Notifications
You must be signed in to change notification settings - Fork 12k
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
Escrows #1014
Escrows #1014
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome @nventuro! I love this "Escrow" name, it's right on the money.
Here's a first round of feedback.
@@ -3,31 +3,31 @@ pragma solidity ^0.4.24; | |||
|
|||
import "../../math/SafeMath.sol"; | |||
import "./FinalizableCrowdsale.sol"; | |||
import "./utils/RefundVault.sol"; | |||
import "../../payment/RefundEscrow.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think RefundEscrow
is very much crowdsale-specific so it should be with the crowdsale stuff. Potentially in this same file. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disagree: a RefundEscrow
is a payment method, a crowdsale typically using it doesn't make it crowdsale-specific. As an example, platforms like Amazon or eBay also offer refunds and act as an escrow themselves, and could extend their payment systems to add support for multiple payers.
contracts/payment/PullPayment.sol
Outdated
@@ -10,34 +9,34 @@ import "../math/SafeMath.sol"; | |||
* contract and use asyncSend instead of send or transfer. | |||
*/ | |||
contract PullPayment { | |||
using SafeMath for uint256; | |||
Escrow public escrow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this state variable private.
contracts/payment/PullPayment.sol
Outdated
function asyncSend(address dest, uint256 amount) internal { | ||
payments[dest] = payments[dest].add(amount); | ||
totalPayments = totalPayments.add(amount); | ||
function asyncSend(address _dest, uint256 _amount) internal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The word transfer
is quite overloaded but I feel like this is an asyncTransfer
more than an asyncSend
, in analogy with send
vs transfer
because of the lack of return value.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this when updating PullPayment
, I agree that transfer
is indeed the better term.
contracts/payment/RefundEscrow.sol
Outdated
/** | ||
* @title RefundEscrow | ||
* @dev Escrow that holds investor funds for a unique benefitiary, and allows for | ||
* either withdrawal by the benefiatiary, or refunds to the investors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo twice here: "benefitiary" → "beneficiary".
Also, "unique" doesn't sound right... "single" is the right word, but it feels redundant to me anyway.
This description doesn't explain the condition according to which either withdrawal or refunding is allowed. We should also explain that this contract was written as the escrow for a RefundableCrowdsale
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, unique
is just plain wrong. I think it could just say 'for a beneficiary' and the meaning would remain clear.
I'm not sure about the RefundableCrowdsale
part though: supporting refunds in a payment system doesn't scream 'crowdsale' to me. I actually see the inverse of that being true: the RefundableCrowdsale
is nothing more than a crowdsale, the escrow, and glue code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that RefundEscrow
is not necessarily crowdsale-specific. I didn't mean to say that. I'm just interested in cross-linking related contracts. Perhaps this information should be automatically generated by our documentation generator though.
contracts/payment/PullPayment.sol
Outdated
payee.transfer(payment); | ||
/** | ||
* @dev Returns the credit owed to an address. | ||
& @param _dest The creditor's address. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&
→ *
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I blame Stroustrup
contracts/payment/Escrow.sol
Outdated
*/ | ||
function deposit(address _payee) payable public { | ||
uint256 amount = msg.value; | ||
require(amount > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line doesn't add any value (ha!). It will unnecessarily propagate the special case to the users of Escrow
, when we can easily treat deposit(0)
as a no-op. Let's just remove the line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deposit(0)
would indeed do nothing: I thought it better to protect the caller from using the API wrong, though the argument could be made that this would make the contract more expensive for all other users. It's more of a library-wide decision, IMO. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is deposit(0)
is not a wrong use of the API, it's just a no-op. It's not a matter of gas efficiency, it's about not forcing users of Escrow
to add code to treat 0
specially.
We've discussed this before, though I can't remember where, and the decision was to not revert.
contracts/payment/Escrow.sol
Outdated
uint256 payment = deposits[_payee]; | ||
|
||
require(payment != 0); | ||
require(address(this).balance >= payment); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more of an assert
than a require
.
contracts/payment/Escrow.sol
Outdated
function withdraw(address _payee) public { | ||
uint256 payment = deposits[_payee]; | ||
|
||
require(payment != 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels unnecessary as well. I would remove it.
contracts/payment/RefundEscrow.sol
Outdated
/** | ||
* @dev Withdraws the beneficiary's funds. | ||
*/ | ||
function withdraw() public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escrow#withdraw(address)
has the same name but it does something different. Let's name this one something different. beneficiaryWithdraw
? I don't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. On a related note, withdraw
and refund
do the same thing, but withdraw
emits no events. Should we prevent the user from calling the base function (i.e. override and revert)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I hadn't noticed that. Hm... I think we should keep withdraw
instead of adding a new refund
function. In the case of RefundableCrowdsale
, the user will use RefundableCrowdsale#claimRefund
anyway.
contracts/payment/RefundEscrow.sol
Outdated
*/ | ||
function refund(address _investor) public { | ||
uint256 amount = depositsOf(_investor); | ||
super.withdraw(_investor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to use super
here.
contracts/payment/Escrow.sol
Outdated
* @dev Base escrow contract, holds funds destinated to a payee until they | ||
* withdraw them. | ||
*/ | ||
contract Escrow { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add Deposited
and Withdrawn
events. (I'm open to other names.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would make the Refunded
event a bit redundant (since it will also emit an identical Withdrawn
): do we want to remove it?
786ceac
to
3a33ffb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments. I'd also like to do a last review of correctness and security before merging.
contracts/payment/RefundEscrow.sol
Outdated
@@ -67,23 +61,23 @@ contract RefundEscrow is ConditionalEscrow, Ownable { | |||
/** | |||
* @dev Withdraws the beneficiary's funds. | |||
*/ | |||
function withdraw() public { | |||
function beneficiaryWithdraw() public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to get a second/third opinion on this name. Alternative: withdrawToBeneficiary
.
test/payment/Escrow.behaviour.js
Outdated
@@ -6,42 +6,42 @@ require('chai') | |||
.use(require('chai-bignumber')(BigNumber)) | |||
.should(); | |||
|
|||
export default function ([payer1, payer2, payee1, payee2]) { | |||
export function shouldBehaveLikeEscrow([payer1, payer2, payee1, payee2]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be the default export and have a name at the same time. We need a library-wide convention for this. Currently all of the behavio(u)rs use a default export so we should stick to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of the other behaviors give a name to this function though. I'd leave it anonymous and default, and implement the library-wide change in a different PR.
Opened #1027
contracts/payment/RefundEscrow.sol
Outdated
function withdraw(address _refundee) public { | ||
uint256 amount = depositsOf(_refundee); | ||
super.withdraw(_refundee); | ||
emit Refunded(_refundee, amount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove when we add events to Escrow
.
test/payment/RefundEscrow.test.js
Outdated
receipt.logs.length.should.equal(1); | ||
receipt.logs[0].event.should.equal('Refunded'); | ||
receipt.logs[0].args.refundee.should.equal(refundee); | ||
inLogs(receipt.logs, 'Refunded', { refundee }); | ||
receipt.logs[0].args.weiAmount.should.be.bignumber.equal(amount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that the helper returns the event, so you can do
const event = expectEvent.inLogs(receipt.logs, 'Refunded', { refundee });
event.args.weiAmount.should.be.bignumber.equal(amount);
In this way you also stop relying on the event being at index 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat, I missed that bit, ty!
test/payment/RefundEscrow.test.js
Outdated
@@ -1,4 +1,5 @@ | |||
import EVMRevert from '../helpers/EVMRevert'; | |||
import { inLogs } from '../helpers/expectEvent'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preferred usage for better readability is:
import expectEvent from '../helpers/expectEvent';
expectEvent.inLogs(receipt.logs);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that made more sense, but preferred to copy the existing code style :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's both styles, unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more of a "light" approval; I don't have the time to do a deep review, but the architecture is 👍and after a scan all of the code looks chill. Would prefer that @frangio follows up with his previous review before we merge :)
After discussing offline with @frangio, we decided to restrict the API somewhat, making the base I'll be updating the contracts and tests soon. |
…hods to access it.
|
Thanks @nventuro! |
Fixes #901
🚀 Description
Escrow
and reimplementsPullPayment
using it. ThetotalPayments
variable was removed after being deemed uselessConditionalEscrow
abstract contract, and aRefundEscrow
derived contract (similar toRefundVault
, but the beneficiary must callwithdraw
afterclose
is called instead of it being automatic)RefundVault
and updatesRefundableCrowdsale
to useRefundEscrow
npm run lint:all:fix
).