-
Notifications
You must be signed in to change notification settings - Fork 12k
EIP-1167 Clone Factory (#1162) #1361
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
EIP-1167 Clone Factory (#1162) #1361
Conversation
Hey, let me know if do we need any unit tests which proofs it clone factory is cost efficient. |
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.
Hey there @jbogacz, thanks a lot! This is one of the EIPs that I've been looking at with interest, specially considering our work in https://zeppelinos.org/
I'm not entirely sure what the best approach towards testing should be, but I first have some minor questions we should get out of the way.
test/CloneFactory.test.js
Outdated
clonedTwo = await factory.createNew(_clonedSupplyTwo, { from: owner }); | ||
}); | ||
|
||
it('should have correct state after init', async function () { |
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'd be great if we could extract the ERC20
tests into a behavior, and use it here, though I'm not sure we have an issue to track 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.
Hey @nventuro I'm afraid that I'm not getting this concept, could you elaborate further on ?
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.
Sure! We have some behavior
test files, in which how a contract behaves is described: for example, Ownable.behavior
. This makes the tests reusable, and we can call them from the Ownable test
itself, or from any other test suite.
It'd be great if we did this for the proxy tests (since we'd be running the same tests, making sure everything works properly), but we have not extracted them into a behavior yet - we can do that in a follow-up PR though, so it's nothing to worry about here :)
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.
Oh I see, so I'll leave it for now.
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.
Thanks @jbogacz! We may want to revisit the contract names in the future (I'm not sure master
and clone
are good terms for what's going on, specially since nothing is ever cloned, and IMO it implies storage would also be copied), but they are fine for now, and we should focus on the tests first.
I think the ones in this PR are a bit too complex (with the initFactory
function, etc.), when there's no real need for them to be, and you're using before
blocks, making all tests interdependent. We usually exclusively use beforeEach
, and setup what we want to test in each it
block: take a look at the other test files to get an idea of our style, the one for BreakInvariantBounty
comes to mind as an example you may want to get inspired from :)
constructor(address _masterAddress) public { | ||
masterAddress = _masterAddress; | ||
constructor() public { | ||
masterAddress = new ERC20Mock(msg.sender, 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.
Consider adding a comment here, stating that this constructor will only run in the master
context, and not for all other created proxies.
} | ||
|
||
function createNew(uint256 amount) public returns (address) { | ||
address cloneAddress = createClone(masterAddress); | ||
emit CloneCreated(cloneAddress); | ||
ERC20Mock(cloneAddress).mint(msg.sender, 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.
It's interesting that we're able to replicate the contents of the constructor with this call, but it's only available because this is a mock, were we need to expose that internal method: I wouldn't want someone to look at this and think this is good code they should copy.
I'm actually not sure how we should handle this, since we can't even use ERC20Mintable
because there will be no minters. @frangio WDYT? Maybe test this with something that is constructor-less?
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.
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.
Hey @nventuro sorry for that, I will double check other tests again and this time try to stick general style. As always I appreciate your constructive feedback :) |
* Initialize master contract inside factory mock constructor * Invoke CloneCreated event inside CloneFactory * Add unit test to validate clone instance bytecode
Hey @nventuro, I've simplified CloneFactory.test.js to use SimpleToken as example. I've left one test case too. I'd like to state with you proper way for this test file. I also stayed with initFactory function but simplified it to be more readable. However I'd like to keep this function as a wrapper that helps to retrieve Truffle contract at clone address and keep this operation capsulated. Let me know your thoughts or ideas. Looking forward to you review! :) |
Hi @nventuro, I believe you are busy but could you put any comments here ? As mentioned before I switched to use SimpleToken as example contract in unit test. What is your opinion and acceptable path to follow with this feature? Looking forward to hear your comments here ;) |
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.
@jbogacz sorry for taking so long to review this! Last couple weeks were a bit crazy, with devcon and all, plus we're releasing OpenZeppelin 2.1 soon :)
I made a couple suggestions on the test file to make it simpler to read. We may want to review the naming of the contract, since both 'proxy' and 'clone' are used somewhat interchangeably and may convey different meanings. In particular, 'clone' implies storage is copied, IMO.
|
||
it('should create proxy contract with valid bytecode', async function () { | ||
const proxy = await factory.createNew(); | ||
(await readContractCode(proxy.address)).should.be.equal(expectedBytcode()); |
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 that the event was emitted, using our expectEvent
test helper.
|
||
describe('factory initialized', function () { | ||
beforeEach(async function () { | ||
factory = await initFactory(); |
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.
Since #1380 we've stored this kind of stuff in this
, it'd be great for consistency if we kept doing that :)
contract('CloneFactory', function ([_, owner]) { | ||
let factory; | ||
|
||
describe('factory initialized', function () { |
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 drop this describe
block for now, no need to make the test suite too complicated.
}); | ||
}); | ||
|
||
const initFactory = async function () { |
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 wouldn't abstract this (along withreadContractCode
and expectedBytecode
) into standalone functions until we actually need them (e.g. to prevent repeated code): I'd rather the tests be as explicit and simple as possible, and this obscures them somewhat.
🚀 Description
Introduced CloneFactory implementation consistent with EIP-1167. Based on example implementation and usage from optionality/clone-factory.
Fixes #1162.
npm run lint:fix
).