-
Notifications
You must be signed in to change notification settings - Fork 212
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
Agent: Merge with Fundraising's Pool #941
Conversation
Thanks for opening this pull request! Someone will review it soon 🔍 |
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.
Looking really good!
apps/agent/contracts/Agent.sol
Outdated
// and their balances have not been modified and return the call's return data | ||
for (uint256 j = 0; j < _protectedTokens.length; j++) { | ||
require(protectedTokens[j] == _protectedTokens[j], ERROR_PROTECTED_TOKENS_MODIFIED); | ||
require(balances[j] == balance(_protectedTokens[j]), ERROR_BALANCE_NOT_CONSTANT); |
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 do you think about allowing the token balance to increase when safe executing? It could be useful for use cases such as claiming rewards.
require(balances[j] == balance(_protectedTokens[j]), ERROR_BALANCE_NOT_CONSTANT); | |
require(balance(_protectedTokens[j] >= balances[j]), ERROR_BALANCE_NOT_CONSTANT); |
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 it makes sense indeed ... Gonna update that.
|
||
if (result) { | ||
// if the underlying call has succeeded, we check that the protected tokens | ||
// and their balances have not been modified and return the call's return data |
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.
Perhaps let's require that _protectedTokens.length == protectedTokens.length
. I don't think this can be exploited, but we could fail with a nicer error than accessing an out of bounds item in the array.
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. This could also allow us to make sure no protected tokens has been added [though i'm not sure this could be an attack vector].
apps/agent/contracts/Agent.sol
Outdated
* @return Exits call frame forwarding the return data of the executed call (either error or success data) | ||
*/ | ||
function safeExecute(address _target, bytes _data) external auth(SAFE_EXECUTE_ROLE) { | ||
address[] memory _protectedTokens = new address[](protectedTokens.length); |
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 could cache protectedTokens.length
into a stack variable so we don't read it from storage every time.
apps/agent/contracts/Agent.sol
Outdated
event PresignHash(address indexed sender, bytes32 indexed hash); | ||
event SetDesignatedSigner(address indexed sender, address indexed oldSigner, address indexed newSigner); | ||
|
||
/** | ||
* @notice Safe execute '`@radspec(_target, _data)`' on `_target` |
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.
Maybe let's have a more clear description of what the function does.
* @notice Safe execute '`@radspec(_target, _data)`' on `_target` | |
* @notice Execute '`@radspec(_target, _data)`' on `_target` (without allowing transfers of protected tokens) |
Not super convinced about the suggestion, but I would go for something like this
apps/agent/contracts/Agent.sol
Outdated
* @param _data Calldata for the action | ||
* @return Exits call frame forwarding the return data of the executed call (either error or success data) | ||
*/ | ||
function safeExecute(address _target, bytes _data) external auth(SAFE_EXECUTE_ROLE) { |
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 would add the same parameters to the role as EXECUTE_ROLE
has. For _value
it could either be always 0 or we could remove it as a parameter altogether.
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.
Sounds good.
apps/agent/contracts/Agent.sol
Outdated
for (uint256 i = 0; i < protectedTokens.length; i++) { | ||
address token = protectedTokens[i]; | ||
// we don't care if target is ETH [0x00...0] as it can't be spent anyhow [though you can't invoke anything at 0x00...0] | ||
require(_target != token || token == ETH, ERROR_TARGET_PROTECTED); |
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's the rationale behind allowing to add ETH
to the protected token list?
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 was mostly a convenient thing for us regarding our use case: basically the Controller
could then expose an addCollateralToken
function which would forward the call to MarketMaker
, Tap
and Pool
[no matter whether this is an ERC20 or ETH].
You're right that it does not make a lot of sense into the Agent
as such. I will update it and handle this specific use case in the Controller
contract.
apps/agent/contracts/Agent.sol
Outdated
function removeProtectedToken(address _token) external auth(REMOVE_PROTECTED_TOKEN_ROLE) { | ||
require(tokenIsProtected(_token), ERROR_TOKEN_NOT_PROTECTED); | ||
|
||
_removeProtectedToken(_token); |
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.
_removeProtectedToken(_token); | |
_removeProtectedToken(_token); |
apps/agent/contracts/Agent.sol
Outdated
*/ | ||
function addProtectedToken(address _token) external auth(ADD_PROTECTED_TOKEN_ROLE) { | ||
require(protectedTokens.length < PROTECTED_TOKENS_CAP, ERROR_TOKENS_CAP_REACHED); | ||
require(isContract(_token) || _token == ETH, ERROR_TOKEN_NOT_ETH_OR_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.
What if in addition to ensuring that _token
is a contract, we perform a balance(_token)
to make sure that the balance check doesn't revert for the token. If a token is added and token.balanceOf()
reverts, it will block safeExecute
until the token is removed from the protected list.
A malicious token could still be added that starts reverting after a state change or some period of time. To prevent this we could add a public function (without authentication) to remove a token that reverts, but this is a bit of an overkill IMO.
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 if in addition to ensuring that _token is a contract, we perform a balance(_token) to make sure that the balance check doesn't revert for the token. If a token is added and token.balanceOf() reverts, it will block safeExecute until the token is removed from the protected list
Hmmmm. Yeah. I will add that too.
A malicious token could still be added that starts reverting after a state change or some period of time. To prevent this we could add a public function (without authentication) to remove a token that reverts, but this is a bit of an overkill IMO.
Yeah I think it will mostly make things harder to understand [regarding roles and all] for end users while the treat is really low ...
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.
Thinking about it: what exactly is the threat model here?
-
If we are trying to deal with malicious tokens : what about the situation where calling
balanceOf
would actually empty your balance ? It's unlikely cause if the token is malicious / not trustless it could directly move your tokens [which would be pointless cause they would enp be worth nothing in the end]. -
We are just trying to prevent users to do a mistake while copy / pasting the address of the token ?
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 me the threat model is adding a contract to the list of protected tokens that can brick safeExecute
until the token is removed. This could happen by mistake or maliciously and it could be specially bad if the permission to remove a token from the list is assigned to a long/complicated process or even burned.
But as you point out, adding a malicious token could bypass any check that we add here, so we would really just be protecting from mistakes.
what about the situation where calling balanceOf would actually empty your balance ?
This shouldn't even be a problem since balance(_token)
uses a STATICCALL
.
I just updated the roles in the |
… script execution
@izqui All comments should be fixed now [excepts for the ERC20 check thing cause i'm waiting for your answer on that]. |
apps/agent/test/agent_shared.js
Outdated
}) | ||
|
||
context('> and target is not a protected ERC20 but action affects a protected ERC20 balance', () => { | ||
it('it should revert', async () => { |
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 could add a test case for the Agent's balance increasing as a result of the execution.
apps/agent/test/agent_shared.js
Outdated
}) | ||
|
||
context('> and target is not a protected ERC20 but action affects a protected ERC20 balance', () => { | ||
it('it should revert', async () => { |
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 also test the execution modifying the protected token list which should cause a revert.
apps/agent/contracts/Agent.sol
Outdated
*/ | ||
function safeExecute(address _target, bytes _data) external authP(SAFE_EXECUTE_ROLE, arr(_target, uint256(getSig(_data)))) { | ||
uint256 protectedTokensLength = protectedTokens.length; | ||
address[] memory _protectedTokens = new address[](protectedTokensLength); |
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.
address[] memory _protectedTokens = new address[](protectedTokensLength); | |
address[] memory protectedTokens_ = new address[](protectedTokensLength); |
We normally use this notation when the variable is not an argument to the function (and we need to add an _
)
Added all the test cases plus some additionnal checks for ERC20. For now the contract just calls Do you think it's worth making a low level call to catch the error and return a clearer error message like |
@osarrouy I don't think it is worth it to do the low-level call, that error is fine IMO. |
Agent enhancements
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've just added a number of commits to make the code style consistent with the rest of the apps, improve some of the tests, and add minor optimizations / features.
It would be great if you could take a quick look again through the new commits @izqui!
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.
Amazing work @sohkai. LGTM!
Fixes #656.