Closed izqui closed 7 years ago
Hello @izqui , I would like to tackle this one! Although the solution itself is quite simple, I'm a bit afraid to put things in the wrong place, given the somewhat complex architecture.
My main question is, should I implement a whitelistToken(address token)
function in the bylaw app itself? Because it seems a bit off place, as the bylawas app mostly defines the types of ByLaws and how to resolve them.
And related to this, the whitelistToken(address token)
function itself should be restricted, or it wouldn't make a difference because the malicious token could call it, and therefore should be govern by a bylaw policy, but which one? Or should be open by default and linked to a specific kind later on?
Thanks in advance for the help!
Hello @JGcarv, sorry for the delay.
I think it would make sense to make it as a 'global setting' to all bylaws, an automatically reject actions in which the token is not in the whitelist.
The whitelistToken(...)
function should definitely be protected with a onlyDAO
modifier so it gets protected with bylaws (how meta ;))
Thanks for the responde @izqui!
Just a few more question: What should be the behavior of tokenized eth? Allowed by default?
Also, when dealing with ether, canPerformAction
is always called with 0
for token
or it sometimes uses EtherToken address
?
That's a very good question.
There are two different cases:
Even though the EtherToken should be explicitly whitelisted if trusted to do token transfers, it could be whitelisted by default configuration.
A problem with the whitelist is that it will block all token deposits from not whitelisted tokens (which I don't think it is the expected behavior).
The problem solved with this is not allowing impersonations because of using untrusted tokens, so there is no problem to allow transactions that don't execute anything.
This would require changing the Kernel dispatch function checks order like this:
Current:
function dispatch(address _sender, address _token, uint256 _value, bytes _payload) internal {
require(canPerformAction(_sender, _token, _value, _payload));
vaultDeposit(_token, _value); // deposit tokens that come with the call in the vault
if (_payload.length == 0) return; // Just receive the tokens
New:
function dispatch(address _sender, address _token, uint256 _value, bytes _payload) internal {
vaultDeposit(_token, _value); // deposit tokens that come with the call in the vault
if (_payload.length == 0) return; // Just receive the tokens
require(canPerformAction(_sender, _token, _value, _payload));
Also receiveApproval(address _sender, uint256 _value, address _token, bytes _data)
method is sometimes used even if the token is not calling it (see we use the _token
parameter as the token).
This allows for trustless deposits of tokens (while being able to account for it). The problem is that the _sender
parameter and _data
can only be trusted that it came from the token in the case that _token == msg.sender
.
I would modify the dispatch call in receiveApproval
to: dispatch(_token == msg.sender ? _sender : msg.sender, _token, _value, _token == msg.sender ? _data : new bytes(0));
Thanks for the answer! I'm already testing the modifications, but I'm seriously stuck here, for some reason, I cannot make it work.
Now, reading the code, I stumbled across a comment in VaultOrgan.sol
that says:
@dev deposit is not reachable on purpose using normal dispatch route
What does that mean? It should be reachable, since the dispatch
in Kernel.sol
calls vaultDeposit
, which makes the deposit correct? Or I am missing something? Can this be the reason the deposits are not working on the tests?
I've investigated a bit, and I feel like there's a problem with the deposit function in the VaultOrgan
. Look:
function deposit(address _token, uint256 _amount)
check_blacklist(_token)
external
payable
{
if (_amount == 0)
return;
if (_token == 0 && msg.value == _amount)
tokenizeEther(_amount); // if call has ETH, we tokenize it
address token = _token == 0 ? getEtherToken() : _token;
uint256 currentBalance = getTokenBalance(token);
// This will actually be dispatched every time balance goes from 0 to non-zero.
// The idea is that the frontend can listen for this event in all DAO history.
// TODO: Is an event for when a certain token balance goes to 0 needed?
if (currentBalance == 0)
NewTokenDeposit(token);
// TODO: Aragon Network funds redirect goes here :)
uint256 newBalance = safeAdd(currentBalance, _amount); // - aragonNetworkFee;
// Check token balance isn't less than expected.
// Could be less because of a faulty erc20 implementation (can't trust)
// Could be more because a token transfer can be done without notifying
assert(newBalance <= ERC20(token).balanceOf(this));
setTokenBalance(token, newBalance);
Deposit(token, dao_msg().sender, _amount);
}
If the token in question is an ERC223 token, it implements the transfer
function that calls tokenFallback
, which is implemented in the Kernel
and calls the deposit
function above.
Now , in the assert(newBalance <= ERC20(token).balanceOf(this));
line the token transfer transaction hasn't yet finished, so the balanceOf(this)
is still a lower value than the newBalance
making the whole transaction fail.
Most ERC223 implementations (including the one in test/helpers) change the token balance before making the call to the receiver, therefore even though the call hasn't 'finished' if the receiver were to make a check of the balance, it should already be updated.
Also why do you say deposits are not working on the tests? All tests from master are passing AFAIK, including the 'test/dao_dispatch.js' tests where erc223 is tested
Damn, that makes sense. Now I don't really know what I was thinking before to come to that conclusion.
Thanks for the insight
Did you solve it? If you published a draft PR i could try to help finding the issue!
Hi @JGcarv! How is progress going? i'd love to get this merged this week
Hey! I haven't done any progress, to be honest.
Somehow testing this contract is pretty frustrating, as nothing behaves as I expected(And I tried many variations).
I'll make a PR in a minute with my current state and hopefully you can shed some light on this.
Tokens are arbitrary contracts that can run malicious code. Some sort of whitelist has to be implemented for the tokens that are allowed to execute transactions in the DAO.
There is an attack possible where you can create a fake token contract and make transactions to the DAO as another person. Only tokens with trusted code (that doesn't allow impersonation) should be added to this whitelist.
I think a good solution for this would be to implement a token whitelist in BylawsApp, and return false in
canPerformAction
if the token that performed the action is not in the whitelist (and therefore the sender cannot be trusted)