Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upTransparent Proxy #36
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()); |
spalladino
Apr 12, 2018
Member
Wouldn't this break the createAndCall methods?
Wouldn't this break the createAndCall methods?
facuspagnuolo
Apr 12, 2018
Contributor
I think it shouldn't since the factory performs the call and then transfers the ownership to the requested owner
I think it shouldn't since the factory performs the call and then transfers the ownership to the requested owner
spalladino
Apr 13, 2018
Member
Still, I believe it's a bit too brittle. I'd consider removing this check.
Still, I believe it's a bit too brittle. I'd consider removing this check.
frangio
Apr 13, 2018
Author
Contributor
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.
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. | |||
fiiiu
May 7, 2018
Contributor
update docs to reflect current caller-dependent behavior.
update docs to reflect current caller-dependent behavior.
| function implementation() public view returns (address) { | ||
| return _implementation(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Sets the address of the owner | ||
| */ |
fiiiu
May 7, 2018
•
Contributor
NIT: L59, setUpgradeabilityOwner -> _setUpgradeabilityOwner? See also calls in L24 & L73.
NIT: L59, setUpgradeabilityOwner -> _setUpgradeabilityOwner? See also calls in L24 & L73.
|
I've rebased the changes on |
|
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? |
|
Thanks fran! Looking really good, left some minor comments |
| @@ -3,3 +3,4 @@ build/ | |||
| lib/ | |||
| coverage/ | |||
| coverage.json | |||
| .node-xmlhttprequest-sync-* | |||
facuspagnuolo
May 11, 2018
Contributor
🙏
| function delegatedFunction() external pure returns (bool) { | ||
| return true; | ||
| } | ||
| } |
facuspagnuolo
May 11, 2018
Contributor
we should move all the mocks for testing purpose to a test folder and npm-ignore it
we should move all the mocks for testing purpose to a test folder and npm-ignore it
| */ | ||
| function getProxyOwner(OwnedUpgradeabilityProxy proxy) public view returns (address) { | ||
| return proxy.proxyOwner(); | ||
| } |
facuspagnuolo
May 11, 2018
Contributor
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
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
facuspagnuolo
May 11, 2018
Contributor
Agree with adding a helper to read the owner from storage directly :)
Agree with adding a helper to read the owner from storage directly :)
alcuadrado
May 13, 2018
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.
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 { |
facuspagnuolo
May 11, 2018
Contributor
we can add an inline comment here explaining what is clashing in this contract
we can add an inline comment here explaining what is clashing in this contract
fiiiu
May 11, 2018
Contributor
+1
+1
| */ | ||
| function () payable external { | ||
| _fallback(); | ||
| } |
facuspagnuolo
May 11, 2018
Contributor
can we move internal functions to the bottom of the contract?
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 { |
facuspagnuolo
May 11, 2018
Contributor
let's use setProxyOwner here
let's use setProxyOwner here
fiiiu
May 11, 2018
Contributor
+1
+1
| proxy.transferProxyOwnership(owner); | ||
| require(proxy.call.value(msg.value)(data)); |
facuspagnuolo
May 11, 2018
Contributor
👍
alcuadrado
May 13, 2018
Nice catch @frangio
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 |
facuspagnuolo
May 11, 2018
Contributor
it would be too implementative in this context IMO... we are already checking storage works as expected
it would be too implementative in this context IMO... we are already checking storage works as expected
facuspagnuolo
May 11, 2018
Contributor
if you agree please remove the TODO note or add an issue to tackle it
if you agree please remove the TODO note or add an issue to tackle it
| assert.equal(value, '0x0000000000000000000000000000000011111142') | ||
| }); | ||
| }); | ||
| }); |
facuspagnuolo
May 11, 2018
Contributor
👏
| */ | ||
| 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 |
fiiiu
May 11, 2018
Contributor
Do we also want a @dev line here? I understand that it will be redundant, but concerned about automated doc generation..
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 { |
fiiiu
May 11, 2018
Contributor
+1
+1
| /** | ||
| * @dev Sets the address of the owner | ||
| */ | ||
| function setUpgradeabilityOwner(address newProxyOwner) internal { | ||
| function _setUpgradeabilityOwner(address newProxyOwner) internal { |
fiiiu
May 11, 2018
Contributor
+1
+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 { | |||
fiiiu
May 11, 2018
Contributor
Do we really need both _upgradeTo() and _setImplementation() or is this just a naming issue?
Do we really need both _upgradeTo() and _setImplementation() or is this just a naming issue?
frangio
May 11, 2018
Author
Contributor
_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.
_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.
frangio
May 11, 2018
•
Author
Contributor
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).
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,
OwnedUpgradeabilityProxycannot 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
onlyProxyOwnerto 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#implementationis 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#proxyOwnercannot be queried by anyone, only by theproxyOwner. This makes it kind of redundant to exposeproxyOwnervia 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.
onlyProxyOwneras the semantics are different from the usualonly*modifiers.getProxyOwnerand test ownership changes differently.OwnedUpgradeabilityProxytests (see comment below).