-
Notifications
You must be signed in to change notification settings - Fork 0
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
Conversation
contracts/Remittance.sol
Outdated
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); |
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.
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
?
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.
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?
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 web3.eth.getTransaction(hash)
. Anyone can do that. Only access to a synchronised node is required.
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.
What I mean by transaction value is the possibility to withdraw the ether in the contract. What are you suggesting?
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.
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.
contracts/Remittance.sol
Outdated
} | ||
|
||
function withdrawFees() public _onlyOwner returns(bool success) { | ||
uint amount = fees; |
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.
Who owns those != 0
fees after an owner change?
contracts/Remittance.sol
Outdated
} | ||
|
||
function withdraw(bytes32 password) public returns(bool success) { | ||
require(password != 0, "Password must be set"); |
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 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.
contracts/Remittance.sol
Outdated
|
||
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"); |
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.
Because of this check, it is probably not necessary to go through the hash calculation. Just passing the hash should be enough.
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.
"by the emitter"
?
contracts/Remittance.sol
Outdated
contract Remittance is Stoppable { | ||
using SafeMath for uint; | ||
|
||
struct Transaction { |
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.
Because transaction has a meaning in Ethereum, perhaps another name?
contracts/Remittance.sol
Outdated
} | ||
|
||
emit LogNewTransaction(msg.sender, exchange, amount); | ||
transactions[hash] = Transaction({time: now.add(CANCELLATION_DELAY), emitter: msg.sender, amount: 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.
Would look nicer over more lines.
contracts/Remittance.sol
Outdated
require(transactions[hash].emitter == address(0), "Password already used"); | ||
|
||
uint amount = msg.value; | ||
if (getOwner() != msg.sender) { |
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.
That is an expensive check. I would say, you can always take fees, even from the owner.
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.
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
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.
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
.
contracts/Remittance.sol
Outdated
} | ||
|
||
fees[getOwner()] = fees[getOwner()].add(TX_FEES); | ||
amount = amount.sub(TX_FEES); |
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 you can save gas by replacing:
uint amount = msg.value;
amount = amount.sub(TX_FEES);
with:
uint amount = msg.value.sub(TX_FEES);
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.
Well spotted!
contracts/Remittance.sol
Outdated
mapping(bytes32 => Transaction) private transactions; | ||
uint private fees; | ||
mapping(bytes32 => Order) private transactions; | ||
mapping(address => uint) private fees; |
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.
Are you intentionally making it difficult for people to know the status of the contract?
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 intentionally wanted to make these variables private to ensure confidentiality
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.
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.
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.
Gotcha this time. Quite difficult to admit that everything is public and tracable!
contracts/Remittance.sol
Outdated
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), |
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.
Cancellation delay could be a parameter passed by the sender. Within reason.
contracts/Remittance.sol
Outdated
transactions[hash].emitter = address(0); | ||
transactions[hashedOTP].amount = 0; | ||
transactions[hashedOTP].time = 0; | ||
transactions[hashedOTP].emitter = address(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.
You could say delete transactions[hashedOTP]
.
Are you intentionally allowing reuse of previously used passwords?
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 forgot this utility ;)
contracts/Remittance.sol
Outdated
|
||
require(amount > 0, "Nothing to withdraw"); | ||
|
||
fees = 0; | ||
fees[getOwner()] = 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.
You could save owner in memory and do SLOAD
only once.
contracts/Remittance.sol
Outdated
@@ -87,11 +86,11 @@ contract Remittance is Stoppable { | |||
} | |||
|
|||
function withdrawFees() public _onlyOwner returns(bool success) { |
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.
_onlyOwner
, so if I am a former owner, I cannot withdraw?
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 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.
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 forgot to delete the modifier when replacing the uint by a mapping
contracts/Remittance.sol
Outdated
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), |
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.
Avoid fixing user mistakes. You don't like the delay? -> revert.
contracts/Remittance.sol
Outdated
uint amount = fees[getOwner()]; | ||
function withdrawFees() public returns(bool success) { | ||
address owner = getOwner(); | ||
uint amount = fees[owner]; |
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.
So only the current owner can withdraw?
If I am the former owner, and I still have something to withdraw, how do I do?
contracts/Remittance.sol
Outdated
using SafeMath for uint; | ||
|
||
struct Order { | ||
uint time; |
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.
deadline
to be more expressive?
contracts/Remittance.sol
Outdated
|
||
uint constant TX_FEES = 2000; | ||
|
||
mapping(bytes32 => Order) public transactions; |
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.
Can you rename to orders
too?
contracts/Remittance.sol
Outdated
mapping(bytes32 => Order) public transactions; | ||
mapping(address => uint) public fees; | ||
|
||
event LogNewTransaction(address indexed emitter, address exchange, uint 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.
LogNewOrder
?
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 all your events should mention the hashed OTP. After all, that's the only thing that helps us follow the lifecycle of an order.
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.
Agree. All excepting LogTakeFes :)
contracts/Remittance.sol
Outdated
|
||
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); |
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.
LogCancelOrder
?
contracts/Remittance.sol
Outdated
fees[getOwner()] = fees[getOwner()].add(TX_FEES); | ||
emit LogTakeFees(msg.sender, TX_FEES); | ||
|
||
emit LogNewTransaction(msg.sender, exchange, 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.
You notice that exchange
here is purely informative. There is no enforcement to ensure its validity.
Perhaps rename it to express 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.
Deleting exchange.
contracts/Remittance.sol
Outdated
|
||
transactions[hash].amount = 0; | ||
transactions[hash].time = 0; | ||
transactions[hash].emitter = address(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.
Not sure, do you want to let emitters reuse the same password?
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.
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.
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.
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.
contracts/Remittance.sol
Outdated
|
||
delete(transactions[hashedOTP]); | ||
|
||
emit LogCancelTransaction(msg.sender, exchange, tx.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.
Here too exchange
is purely informative.
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.
And falsifiable.
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.
Will remove the exchange, in fact the exchange does not need to be pinged if an order is placed
contracts/Stoppable.sol
Outdated
|
||
contract Stoppable is Owned { | ||
|
||
bool isRunning; |
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.
Are you inviting the developer of the child contract to change the value at will?
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.
Indeed, I prefer this design pattern than a real kill switch
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.
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.
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 meant bool private isRunning;
contracts/Stoppable.sol
Outdated
bool isRunning; | ||
|
||
event LogPausedContract(address sender); | ||
event LogResumeContract(address sender); |
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.
indexed
perhaps?
} | ||
|
||
constructor() public { | ||
isRunning = true; |
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.
Can you make this initial value a constructor parameter?
contracts/Remittance.sol
Outdated
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); |
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.
hashedOTP
?
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.
Also, do you think people will filter per hashedOTP from the client side?
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.
To track undergoing transfers yes, I think.
contracts/Remittance.sol
Outdated
address owner = getOwner(); | ||
|
||
fees[owner] = fees[owner].add(TX_FEES); | ||
emit LogTakeFees(msg.sender, TX_FEES); |
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 could be interesting to inform the owner
too.
|
||
emit LogNewOrder(msg.sender, amount, hashedOTP); | ||
orders[hashedOTP] = Order({ | ||
deadline: now.add(delay), |
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 did not discuss it but you have to know that the timestamp is not very accurate, but good enough for this use case.
contracts/Remittance.sol
Outdated
} | ||
|
||
function cancelOrder(bytes32 hashedOTP) public returns(bool success) { | ||
require(hashedOTP != 0, "The secret is not set"); |
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 not necessary as there is no order at 0
. See line 33.
contracts/Remittance.sol
Outdated
|
||
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"); |
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.
"by the emitter"
?
contracts/Remittance.sol
Outdated
|
||
orders[hashedOTP].amount = 0; | ||
orders[hashedOTP].deadline = 0; | ||
orders[hashedOTP].emitter = address(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.
delete orders[hashedOTP]
;
contracts/Remittance.sol
Outdated
mapping(bytes32 => Order) public orders; | ||
mapping(address => uint) public fees; | ||
|
||
event LogNewOrder(address indexed emitter, uint amount, bytes32 hashedOTP); |
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 would be good to have the events be a verb past tense. LogNewOrderPlaced
, LogWithdrawn
, LogOrderCancelled
, LogFeesTaken
.
…o detect the password associated to a given hash
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]); |
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 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.
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.
Patch in one my latest commit :)
contracts/Remittance.sol
Outdated
|
||
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"); |
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 > 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.
Too much comments. Creating new pull request. |
No description provided.