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

Contract #2

Closed
wants to merge 15 commits into from
Closed

Contract #2

wants to merge 15 commits into from

Conversation

Nitr4x
Copy link
Owner

@Nitr4x Nitr4x commented Jul 16, 2019

No description provided.

@Nitr4x Nitr4x mentioned this pull request Jul 16, 2019
Closed
function newTransaction(address exchange, bytes32 password) _onlyIfRunning public payable returns(bool success) {
require(exchange != address(0) && password != 0 && msg.value > 0, "An error occured. Ensure that both exchange and password are set and that your transaction value is above 0");

bytes32 hash = hashOTP(exchange, password);

Choose a reason for hiding this comment

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

You know that because of how consensus works:

  • all transactions are public
  • all transaction data is public
  • all code execution is public

What does it mean for your password?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Indeed, the password can be read in cleartext. However, to be able to retrieve the transaction value, the call must be performed from the exchange wallet. What is the point in your statement?

Choose a reason for hiding this comment

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

No web3.eth.getTransaction(hash). Anyone can do that. Only access to a synchronised node is required.

Copy link
Owner Author

Choose a reason for hiding this comment

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

What I mean by transaction value is the possibility to withdraw the ether in the contract. What are you suggesting?

Choose a reason for hiding this comment

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

For the exercise, Alice sends Ether for Carol to withdraw and give the $ to Bob.
We want Carol to wait for Bob and his secret to be able to withdraw.
In your setup, Carol does not need to wait because the secret is already there. Carol can withdraw without Bob's secret (not a secret).
You have to make it so that Carol has to wait for Bob for the secret.

}

function withdrawFees() public _onlyOwner returns(bool success) {
uint amount = fees;

Choose a reason for hiding this comment

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

Who owns those != 0 fees after an owner change?

}

function withdraw(bytes32 password) public returns(bool success) {
require(password != 0, "Password must be set");

Choose a reason for hiding this comment

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

This check is not terribly important. After all, if it is wrong, it will fail on line 77.
So if you remove it, it will be cheaper for successful withdraws, and more expensive for mistakes.


require(tx.amount > 0, "Neither the transaction does not exist or the password is wrong");
require(now >= tx.time, "You must wait 5 days before cancelling the transaction");
require(tx.emitter == msg.sender, "The call must be initiated by the wanted exchange");

Choose a reason for hiding this comment

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

Because of this check, it is probably not necessary to go through the hash calculation. Just passing the hash should be enough.

Choose a reason for hiding this comment

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

"by the emitter"?

contract Remittance is Stoppable {
using SafeMath for uint;

struct Transaction {

Choose a reason for hiding this comment

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

Because transaction has a meaning in Ethereum, perhaps another name?

}

emit LogNewTransaction(msg.sender, exchange, amount);
transactions[hash] = Transaction({time: now.add(CANCELLATION_DELAY), emitter: msg.sender, amount: amount});

Choose a reason for hiding this comment

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

Would look nicer over more lines.

require(transactions[hash].emitter == address(0), "Password already used");

uint amount = msg.value;
if (getOwner() != msg.sender) {

Choose a reason for hiding this comment

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

That is an expensive check. I would say, you can always take fees, even from the owner.

Copy link
Owner Author

Choose a reason for hiding this comment

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

According to the project guidance : make you, the owner of the contract, take a cut of the Ethers. How much? Your call. Perhaps smaller than what it would cost Alice to deploy the same contract herself

Choose a reason for hiding this comment

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

What I mean is:

fees = fees.add(TX_FEES);
uint amount = msg.value.sub(TX_FEES);
emit LogTakeFees(msg.sender, TX_FEES);

No getOwner() != msg.sender.

}

fees[getOwner()] = fees[getOwner()].add(TX_FEES);
amount = amount.sub(TX_FEES);

Choose a reason for hiding this comment

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

I think you can save gas by replacing:

uint amount = msg.value;
amount = amount.sub(TX_FEES);

with:

uint amount = msg.value.sub(TX_FEES);

Copy link
Owner Author

Choose a reason for hiding this comment

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

Well spotted!

mapping(bytes32 => Transaction) private transactions;
uint private fees;
mapping(bytes32 => Order) private transactions;
mapping(address => uint) private fees;

Choose a reason for hiding this comment

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

Are you intentionally making it difficult for people to know the status of the contract?

Copy link
Owner Author

Choose a reason for hiding this comment

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

I intentionally wanted to make these variables private to ensure confidentiality

Choose a reason for hiding this comment

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

Do you know that all data is public? There is no confidentiality. All you are doing is asking people to do a sha3 and a web3.eth.getStorageAt to get the data.
The only people who will manage that are potential attackers.
Best practice: make all data public.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Gotcha this time. Quite difficult to admit that everything is public and tracable!

amount = amount.sub(TX_FEES);
emit LogTakeFees(msg.sender, TX_FEES);
}

emit LogNewTransaction(msg.sender, exchange, amount);
transactions[hash] = Transaction({time: now.add(CANCELLATION_DELAY), emitter: msg.sender, amount: amount});
transactions[hashedOTP] = Order({
time: now.add(CANCELLATION_DELAY),

Choose a reason for hiding this comment

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

Cancellation delay could be a parameter passed by the sender. Within reason.

transactions[hash].emitter = address(0);
transactions[hashedOTP].amount = 0;
transactions[hashedOTP].time = 0;
transactions[hashedOTP].emitter = address(0);

Choose a reason for hiding this comment

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

You could say delete transactions[hashedOTP].

Are you intentionally allowing reuse of previously used passwords?

Copy link
Owner Author

Choose a reason for hiding this comment

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

I forgot this utility ;)


require(amount > 0, "Nothing to withdraw");

fees = 0;
fees[getOwner()] = 0;

Choose a reason for hiding this comment

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

You could save owner in memory and do SLOAD only once.

@@ -87,11 +86,11 @@ contract Remittance is Stoppable {
}

function withdrawFees() public _onlyOwner returns(bool success) {

Choose a reason for hiding this comment

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

_onlyOwner, so if I am a former owner, I cannot withdraw?

Choose a reason for hiding this comment

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

I thought the point of this mapping was that if I was a former owner, I could withdraw my fees after the ownership transfer without having to time my withdraw and ownerChange transactions so that they are close to each other.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I forgot to delete the modifier when replacing the uint by a mapping

emit LogTakeFees(msg.sender, TX_FEES);

emit LogNewTransaction(msg.sender, exchange, amount);
transactions[hashedOTP] = Order({
time: now.add(CANCELLATION_DELAY),
time: now.add((delay == 0) ? CANCELLATION_DELAY : delay),

Choose a reason for hiding this comment

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

Avoid fixing user mistakes. You don't like the delay? -> revert.

uint amount = fees[getOwner()];
function withdrawFees() public returns(bool success) {
address owner = getOwner();
uint amount = fees[owner];

Choose a reason for hiding this comment

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

So only the current owner can withdraw?
If I am the former owner, and I still have something to withdraw, how do I do?

using SafeMath for uint;

struct Order {
uint time;

Choose a reason for hiding this comment

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

deadline to be more expressive?


uint constant TX_FEES = 2000;

mapping(bytes32 => Order) public transactions;

Choose a reason for hiding this comment

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

Can you rename to orders too?

mapping(bytes32 => Order) public transactions;
mapping(address => uint) public fees;

event LogNewTransaction(address indexed emitter, address exchange, uint amount);

Choose a reason for hiding this comment

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

LogNewOrder?

Choose a reason for hiding this comment

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

I think all your events should mention the hashed OTP. After all, that's the only thing that helps us follow the lifecycle of an order.

Copy link
Owner Author

@Nitr4x Nitr4x Jul 21, 2019

Choose a reason for hiding this comment

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

Agree. All excepting LogTakeFes :)


event LogNewTransaction(address indexed emitter, address exchange, uint amount);
event LogTakeFees(address indexed emitter, uint amount);
event LogCancelTransaction(address indexed emitter, address exchange, uint amount);

Choose a reason for hiding this comment

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

LogCancelOrder?

fees[getOwner()] = fees[getOwner()].add(TX_FEES);
emit LogTakeFees(msg.sender, TX_FEES);

emit LogNewTransaction(msg.sender, exchange, amount);

Choose a reason for hiding this comment

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

You notice that exchange here is purely informative. There is no enforcement to ensure its validity.
Perhaps rename it to express that?

Copy link
Owner Author

@Nitr4x Nitr4x Jul 21, 2019

Choose a reason for hiding this comment

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

Deleting exchange.


transactions[hash].amount = 0;
transactions[hash].time = 0;
transactions[hash].emitter = address(0);

Choose a reason for hiding this comment

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

Not sure, do you want to let emitters reuse the same password?

Copy link
Owner Author

@Nitr4x Nitr4x Jul 21, 2019

Choose a reason for hiding this comment

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

Well it's up to them but I don't want to restrict password reuse. Except if an undergoing order is still waiting to be withdawed with the same password / exhange address.

Choose a reason for hiding this comment

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

What happens if Alice reuses a password.
Carol does not need to wait for Bob to come with the password. Even better, if Alice gave the same password to Dan, Carol does not need to wait for Dan to come.
Carol can just withdraw and leave. Alice will not be happy to know that she, or her front-end, has to remember all the passwords she has used in the past.
In this setup, you have to assume that Alice is the less tech savvy person.


delete(transactions[hashedOTP]);

emit LogCancelTransaction(msg.sender, exchange, tx.amount);

Choose a reason for hiding this comment

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

Here too exchange is purely informative.

Choose a reason for hiding this comment

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

And falsifiable.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Will remove the exchange, in fact the exchange does not need to be pinged if an order is placed


contract Stoppable is Owned {

bool isRunning;

Choose a reason for hiding this comment

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

Are you inviting the developer of the child contract to change the value at will?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Indeed, I prefer this design pattern than a real kill switch

Choose a reason for hiding this comment

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

What I mean, is which way do you want the developer of the child contract to use:

isRunning = false;

or

pauseContract();

One emits an event and controls for ownership, the other doesn't.

Choose a reason for hiding this comment

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

I meant bool private isRunning;

bool isRunning;

event LogPausedContract(address sender);
event LogResumeContract(address sender);

Choose a reason for hiding this comment

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

indexed perhaps?

}

constructor() public {
isRunning = true;

Choose a reason for hiding this comment

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

Can you make this initial value a constructor parameter?

event LogNewOrder(address indexed emitter, uint amount, bytes32 hashedOTP);
event LogTakeFees(address indexed emitter, uint amount);
event LogCancelOrder(address indexed emitter, uint amount, bytes32 hashedOTP);
event LogWithdraw(address indexed emitter, uint amount);

Choose a reason for hiding this comment

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

hashedOTP?

Choose a reason for hiding this comment

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

Also, do you think people will filter per hashedOTP from the client side?

Copy link
Owner Author

Choose a reason for hiding this comment

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

To track undergoing transfers yes, I think.

address owner = getOwner();

fees[owner] = fees[owner].add(TX_FEES);
emit LogTakeFees(msg.sender, TX_FEES);

Choose a reason for hiding this comment

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

It could be interesting to inform the owner too.


emit LogNewOrder(msg.sender, amount, hashedOTP);
orders[hashedOTP] = Order({
deadline: now.add(delay),

Choose a reason for hiding this comment

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

We did not discuss it but you have to know that the timestamp is not very accurate, but good enough for this use case.

}

function cancelOrder(bytes32 hashedOTP) public returns(bool success) {
require(hashedOTP != 0, "The secret is not set");

Choose a reason for hiding this comment

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

This is not necessary as there is no order at 0. See line 33.


require(tx.amount > 0, "Neither the transaction does not exist or the password is wrong");
require(now >= tx.time, "You must wait 5 days before cancelling the transaction");
require(tx.emitter == msg.sender, "The call must be initiated by the wanted exchange");

Choose a reason for hiding this comment

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

"by the emitter"?


orders[hashedOTP].amount = 0;
orders[hashedOTP].deadline = 0;
orders[hashedOTP].emitter = address(0);

Choose a reason for hiding this comment

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

delete orders[hashedOTP];

mapping(bytes32 => Order) public orders;
mapping(address => uint) public fees;

event LogNewOrder(address indexed emitter, uint amount, bytes32 hashedOTP);

Choose a reason for hiding this comment

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

It would be good to have the events be a verb past tense. LogNewOrderPlaced, LogWithdrawn, LogOrderCancelled, LogFeesTaken.

require(now >= tx.deadline, "You must wait 5 days before cancelling the transaction");
require(tx.emitter == msg.sender, "The call must be initiated by the emitter");

delete(orders[hashedOTP]);

Choose a reason for hiding this comment

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

I think that even if it was cancelled, the password may have been disclosed to Carol. So I think it still would not be safe to allow reuse.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Patch in one my latest commit :)


function newOrder(bytes32 hashedOTP, uint delay) _onlyIfRunning public payable returns(bool success) {
require(hashedOTP != 0 && msg.value > 0, "An error occured. Ensure that the secret is set and that your transaction value is above 0");
require(delay > 0 days, "Delay should be above 0 day");

Choose a reason for hiding this comment

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

I think > 0 is the same, cheaper.
Also, on line 44, it is used as seconds. Which is good. You don't want conversions to be handled in the EVM.

@Nitr4x
Copy link
Owner Author

Nitr4x commented Jul 25, 2019

Too much comments. Creating new pull request.

@Nitr4x Nitr4x closed this Jul 25, 2019
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.

2 participants