-
Notifications
You must be signed in to change notification settings - Fork 29
Conversation
The tests are failing because of the way But there is kind of a deeper reason why the previous tests may no longer be appropriate. For example one of the failing tests is "OwnedUpgradeabilityProxy upgradeTo: when the sender is not the owner reverts". This is no longer true in the "transparent" model. When the sender is not the owner the behavior will depend on the underlying implementation. This fact makes me doubt if the transparent model is a good one. |
There is another reason for this feature, and that is that function signature clashes are not that uncommon. A method identifier is 4 bytes, which is only 2^32 possible values. For reference, the 4byte directory has currently about 2^15 registered function names. The main problem is that, without the compiler detecting the clash, the underlying function will be uncallable without the developer noticing. An alternative is to build this into a linter to check this kind of issues, but I'd still like to ensure no clashes at a contract level. Another alternative (at contract level) would be to require an extra 256-bit parameter to be included in every call that should be directed to the proxy itself, thus artificially increasing the length of the signature; but I think I prefer your implementation. |
* @dev Redefines Proxy's fallback to disallow delegation when caller is the proxy owner. | ||
*/ | ||
function () public payable { | ||
require(msg.sender != proxyOwner()); |
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.
Wouldn't this break the createAndCall methods?
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 shouldn't since the factory performs the call and then transfers the ownership to the requested 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.
Still, I believe it's a bit too brittle. I'd consider removing this check.
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 remove the check for this reason alone. I think the proxyOwner
should only be allowed to use the proxy opaquely. If this breaks our current implementation of initialization, we may need to revise that part of the model.
I agree that it is brittle in this way and that it needs to be tackled, but I'm going to think of other solutions.
@@ -28,8 +28,11 @@ contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { | |||
* @dev Throws if called by any account other than 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.
update docs to reflect current caller-dependent behavior.
function implementation() public view returns (address) { | ||
return _implementation(); | ||
} | ||
|
||
/** | ||
* @dev Sets the address of 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.
NIT: L59, setUpgradeabilityOwner
-> _setUpgradeabilityOwner
? See also calls in L24 & L73.
8f81183
to
9f590ce
Compare
I've rebased the changes on |
e007c10
to
3c1f279
Compare
This is an important PR so please review it in detail. 🙏 |
The only thing I still have doubts about are the zos-lib/test/upgradeability/OwnedUpgradeabilityProxy.test.js Lines 298 to 299 in 5889da1
We should provide in the Thoughts? |
@frangio would you mind rebasing from master? |
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 fran! Looking really good, left some minor comments
@@ -3,3 +3,4 @@ build/ | |||
lib/ | |||
coverage/ | |||
coverage.json | |||
.node-xmlhttprequest-sync-* |
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.
🙏
function delegatedFunction() external pure returns (bool) { | ||
return 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.
we should move all the mocks for testing purpose to a test folder and npm-ignore 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.
*/ | ||
function getProxyOwner(OwnedUpgradeabilityProxy proxy) public view returns (address) { | ||
return proxy.proxyOwner(); | ||
} |
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 kind of redundant, isn't it? We are asking the owner of the proxy to tell us the owner of the proxy :p
But I guess we will need it for consistency tho
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! See this comment: #36 (comment)
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 with adding a helper to read the owner from storage directly :)
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'd go with both options. The helper is needed when BaseAppManager
is not used, but it's somewhat obscure. If an app manager is used, the symmetry between getProxyOwner
and getProxyImplementation
would be helpful for newcomers.
pragma solidity ^0.4.21; | ||
|
||
|
||
contract ClashingImplementation { |
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 can add an inline comment here explaining what is clashing in this 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.
+1
contracts/upgradeability/Proxy.sol
Outdated
*/ | ||
function () payable external { | ||
_fallback(); | ||
} |
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 move internal functions to the bottom of the contract?
/** | ||
* @dev Sets the address of the owner | ||
*/ | ||
function setUpgradeabilityOwner(address newProxyOwner) internal { | ||
function _setUpgradeabilityOwner(address newProxyOwner) internal { |
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.
let's use setProxyOwner
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.
+1
proxy.transferProxyOwnership(owner); | ||
require(proxy.call.value(msg.value)(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.
👍
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.
Nice catch @frangio
|
||
const proxyOwner = await this.proxy.proxyOwner() | ||
it('assigns new proxy owner', async function () { | ||
// [TODO] maybe we should test this by reading from storage |
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 too implementative in this context IMO... we are already checking storage works as expected
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.
if you agree please remove the TODO
note or add an issue to tackle it
assert.equal(value, '0x0000000000000000000000000000000011111142') | ||
}); | ||
}); | ||
}); |
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.
👏
contracts/upgradeability/Proxy.sol
Outdated
*/ | ||
contract Proxy { | ||
/** | ||
* @dev Tells the address of the implementation where every call will be delegated. | ||
* @return address of the implementation to which it will be delegated | ||
* @return address of the implementation to which the fallback delegates all calls |
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 we also want a @dev
line here? I understand that it will be redundant, but concerned about automated doc generation..
pragma solidity ^0.4.21; | ||
|
||
|
||
contract ClashingImplementation { |
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.
+1
/** | ||
* @dev Sets the address of the owner | ||
*/ | ||
function setUpgradeabilityOwner(address newProxyOwner) internal { | ||
function _setUpgradeabilityOwner(address newProxyOwner) internal { |
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.
+1
@@ -52,7 +56,7 @@ contract UpgradeabilityProxy is Proxy { | |||
|
|||
/** | |||
* @dev Upgrades the implementation address | |||
* @param newImplementation The address of the new implementation to be set | |||
* @param newImplementation representing the address of the new implementation to be set | |||
*/ | |||
function _upgradeTo(address newImplementation) internal { |
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 we really need both _upgradeTo()
and _setImplementation()
or is this just a naming issue?
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.
_setImplementation()
should be a dumb setter for the org.zeppelinos.proxy.implementation
slot, which is why it's private
. I'm going to move the other logic to _upgradeTo
and add comments explaining.
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 ended up leaving the isContract
check in the otherwise "dumb" setter because it's quite crucial and in fact it initially led me to a bug which was hard to pin down (but correctly caused the tests to fail).
@fiiiu @facuspagnuolo All review comments addressed! 🚀 |
…sparent-proxy Transparent Proxy
The idea explored here is to make the Proxy entirely transparent to a normal user, removing the conflicts between the public administrative interface of the proxy itself, and the interface of the underlying proxied contract.
For example,
OwnedUpgradeabilityProxy
cannot have a public functionowner()
, because the proxied contract may have a function with the same signature. If both the proxy and proxied contracts had a functionowner()
, any such call would be caught by the proxy and not be delegated.We solve this situation essentially by modifying
onlyProxyOwner
to execute the administrative function if the caller is theproxyOwner
, and otherwise delegate the call to the proxied contract.https://github.com/zeppelinos/core/blob/8f81183f4de074fb2359ceab285c2cbf085a7c3a/contracts/upgradeability/OwnedUpgradeabilityProxy.sol#L30-L36
There is a caveat, which is that
Proxy#implementation
is now only accessible by theproxyOwner
. When the proxy owner is a contract that manages upgrades, such asProjectController
, it should expose this information about its owned proxies.https://github.com/zeppelinos/core/blob/8f81183f4de074fb2359ceab285c2cbf085a7c3a/contracts/ProjectController.sol#L163-L165
Similarly,
OwnedUpgradeabilityProxy#proxyOwner
cannot be queried by anyone, only by theproxyOwner
. This makes it kind of redundant to exposeproxyOwner
via the owner contract (like we can do withimplementation
). If someone wished to query who is the owner for a particular proxy they would have to read the storage location directly, or look for proxy ownership events originating from its address.I'd like to hear everyone's thoughts on this.
onlyProxyOwner
as the semantics are different from the usualonly*
modifiers.getProxyOwner
and test ownership changes differently.OwnedUpgradeabilityProxy
tests (see comment below).