-
Notifications
You must be signed in to change notification settings - Fork 53
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.
As mentioned in the issue, this could be implemented via settle
as well, but I think it's nice for convenience to expose this method directly.
.solhint.json
Outdated
@@ -3,6 +3,7 @@ | |||
"plugins": ["prettier"], | |||
"rules": { | |||
"compiler-version": ["error", "^0.6.12"], | |||
"prettier/prettier": "error" | |||
"prettier/prettier": "error", | |||
"reason-string": ["warn",{"maxLength":33}] |
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.
why 33? Seems like an odd value for max line length. Should we use either 79 or 99 as suggested by the solidity style guid
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 chose 33 because 32 was the default and I needed 33. Thought we could increase it as necessary, but also happy to go with 79.
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 this should stay 32. This reduces the amount of memory needed to encode the string for the error message to one word (why it was chosen to be 32 in the first place). Revert messages don't have to be descriptive, just unique.
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.
Oops, I thought this was line length.
test/GPv2Settlement.test.ts
Outdated
await waffle.deployContract(deployer, ERC20, ["token0", "18"]), | ||
await waffle.deployContract(deployer, ERC20, ["token1", "18"]), |
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 we use waffle's mock contracts here instead of real implementations?
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 guess... but why?
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 in unit tests it's good practice to use mocks instead of "real objects" as your dependencies. This allows to isolate the behaviour you are trying to test and set clear instructions on what your dependencies are expected to do and how your object under test should react. If you test multiple components together, you are likely writing an integration test.
While for ERC20 it might be easy enough to use a fake implementation to get the desired behavior of our dependencies, this is not the case for more complex dependencies and as such it's more consistent to also mock ERC20 (e.g. as we do in dex-contracts).
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.
Isn't it just going be more setup to create a mock contract and tell it what it is supposed to do at every step of the way instead of just having the real thing. Seems to me like the real thing is always better and (in this case) overall shorter to setup. I will look into this in the morning, perhaps I just don't get it.
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.
One more argument is that we probably want to have tests to make sure this works with non-standard ERC20 implementations (such as transfer reverting vs returning false). If we use the OpenZeppelin implementation for tests, thats not really possible.
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.
Real objects require future maintainers of the test to understand how the inner logic of the dependency works in order to understand the setup of the test (which in the case of ERC20 can be assumed trivial but in more complicated cases is not). Setting up mocks should be as easy as saying if this method gets called, return this
. It can even be as easy as sysing all calls should pass
(e.g. return true for ERC20).
src/contracts/GPv2Settlement.sol
Outdated
address[] memory tokenAddresses, | ||
address receiver | ||
) public onlyOwner { |
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 making this function external and storing the array in calldata would make the withdraw transaction cheaper.
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 can surely make both of these suggestions. In fact I meant to use calldata
, but it got changed while fighting with failing testcases.
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.
Yes, external
is better here IMO since we don't ever intend on calling this internally.
src/contracts/GPv2Settlement.sol
Outdated
address[] memory tokenAddresses, | ||
address receiver | ||
) public onlyOwner { |
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.
Yes, external
is better here IMO since we don't ever intend on calling this internally.
test/GPv2Settlement.test.ts
Outdated
await waffle.deployContract(deployer, ERC20, ["token0", "18"]), | ||
await waffle.deployContract(deployer, ERC20, ["token1", "18"]), |
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.
One more argument is that we probably want to have tests to make sure this works with non-standard ERC20 implementations (such as transfer reverting vs returning false). If we use the OpenZeppelin implementation for tests, thats not really possible.
Alright, @nlordell and @fleupold - I have looked into mocking the token contract and landed on the following snippet that just make absolutely no sense to me because I can't see how anything is actually being tested: const mockTokenTransfer = async (
token: MockContract,
sender: string,
receiver: string,
amount: BigNumber,
): Promise<void> => {
const senderCurrentBalance = await token.balanceOf(sender);
const receiverCurrentBalance = await token.balanceOf(receiver);
assert(senderCurrentBalance.sub(amount) >= 0);
await token.mock.balanceOf
.withArgs(sender)
.returns(senderCurrentBalance.sub(amount));
await token.mock.balanceOf
.withArgs(receiver)
.returns(receiverCurrentBalance.add(amount));
};
TEST:
const mintAmount = 1337;
const token = await waffle.deployMockContract(deployer, ERC20.abi);
await token.mock.transfer.returns(true);
await token.mock.balanceOf
.withArgs(settlement.address)
.returns(mintAmount);
await token.mock.balanceOf.withArgs(owner.address).returns(0);
// Assertions about the balance (which are obviously correct)
// Perform the operation
await settlement.transferBalanceTo(
[token.address],
owner.address,
);
// Update the state of the token after transfer
await mockTokenTransfer(token, settlement.address, owner.address, mintAmount);
// Assertions about the balances (again, obviously true) I'm basically just setting the state however I want it so that everything will always work. Can you please tell me what I don't understand about this? |
@bh2smith you should not need the
Which would test that indeed the entirety of |
OK then! Yes thanks this is what I was missing. So we only need to test that functions I had expected to be called were called. |
ahm... sorry for not reviewing earlier. But I think we should keep the contract as simple and elegant as possible. Hence, I would not expose another function besides the existing settle function to transfer funds. I think the convenience is not worth it. Also, withdraws will be very easy to identify later, as they will just send funds to the address of the dao. |
I don't wanna make the call. I wanna know the opinion of others. But I think we should not have this function ... I also don't like the modifier "onlyOwner". I would prefer an "onlySolver" modifier for the settle function and not talk about any owners. |
@josojo Do you think this should be implemented via |
So essentially, once |
A solver could also call the settlement function with the only intend to withdraw the funds. As long as the receiving address is the dao, this is legit... |
In the end, it depends on how much of an overhead it is to call |
I have hoped for the fact that we don't need an owner for the settlement contract. Maybe the allowance manager, in order to be upgradeable, but not for the settlement contract. |
As mentioned in the PR description and the "TODO" message in the contract, this is only temporary and the AccessControl will be introduced in a future PR #120. |
I'm also against any owner in the sense of "somebody who can change the code of the contract," for all contracts involved. |
I would like to move past the concept of ownership introduced here (only temporarily as a placeholder) as it is not directly relevant to the context of this PR. The plan, moving forward it to rename this modifier to The purpose of this PR is only to implement the If it would make things easier, I could rename the modifier to |
b5f5c3e
to
b7b08a6
Compare
I thought we can manage this solvers in an external contract. And yes the external contract can have another owner. |
Closing in favour of not exposing such a method, since the same functionality can be implemented via "Interactions" |
Fixes #17
At this moment, we introduce a function
transferBalanceTo
which accepts a list of token addresses and receiver address. The function then transfers its balance of each listed token to the receiver address. This is, of course, restricted by the "onlyOwner" modifier which remains unimplemented, but will be taken care of in a separate PR via the introduction of anAccessControl
contract in #120.Test Plan
Unit tests.