Skip to content
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

Update security roles on v2 contracts #117

Merged
merged 25 commits into from
Jan 3, 2019
Merged

Conversation

patitonar
Copy link
Contributor

Closes #111

Since we are going to perform one upgrade on deployed contracts of ERC20-To-Native, this PR includes changes from #112.

To review specific changes from this PR, refer to commit abbde31

Once we agree on changes, these are the next steps:

  • Prepare gists files for upgrade
  • Test performing an upgrade of deployed contract
  • Update deploy script, .env.example and deploy/README.md file

@ghost ghost added the in progress label Dec 11, 2018
@ghost ghost removed the in progress label Dec 11, 2018
@akolotov
Copy link
Collaborator

In general, I am OK with changes. Let's prepare a gist files for the erc-to-native mode in order to upgrade the ETH-xDai bridge.

@patitonar
Copy link
Contributor Author

Here are the Gist files for each contract. I also described one method for each contract to be called when upgrading the contract to make the necessary updates for limits and new Owner role. Please check the values of the limits and update them as needed, also regarding the Owner, another option is to receive the new owner address as a parameter instead of using current validatorContract's owner.

Once we agree on the details of methods, I'll add them to the gist files.

Home Bridge Gist: https://gist.github.com/patitonar/39ac4d337420864bc912123f72bc41f7

// Method for Home Bridge
function upgradeToV220() public onlyProxyOwner {
  require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
  setOwner(validatorContract().owner());
  uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
  emit ExecutionDailyLimitChanged(3000000 ether);
  uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
  boolStorage[keccak256("isUpgradedToV220")] = true;
}

Foreign Bridge Gist: https://gist.github.com/patitonar/36a6e03c3ca13b912064b7742b5c3f5c

// Method for Foreign Bridge
function upgradeToV220() public onlyProxyOwner {
  require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
  setOwner(validatorContract().owner());
  uintStorage[keccak256(abi.encodePacked("maxPerTx"))] = 100000 ether;
  uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
  emit ExecutionDailyLimitChanged(3000000 ether);
  uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
  boolStorage[keccak256("isUpgradedToV220")] = true;
}

@akolotov
Copy link
Collaborator

Thanks!
1.

emit ExecutionDailyLimitChanged(3000000 ether);

Should be with the value 1000000 ether as well in both methods.

another option is to receive the new owner address as a parameter

Let's do in this way.

@patitonar
Copy link
Contributor Author

Fixed the methods and added them into the Gist files. Here are the updated version:

// Method for Home Bridge
function upgradeToV220(address _newOwner) public onlyProxyOwner {
  require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
  setOwner(_newOwner);
  uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
  emit ExecutionDailyLimitChanged(1000000 ether);
  uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
  boolStorage[keccak256("isUpgradedToV220")] = true;
}
// Method for Foreign Bridge
function upgradeToV220(address _newOwner) public onlyProxyOwner {
    require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
    setOwner(_newOwner);
    uintStorage[keccak256(abi.encodePacked("maxPerTx"))] = 100000 ether;
    uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
    emit ExecutionDailyLimitChanged(1000000 ether);
    uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
    boolStorage[keccak256("isUpgradedToV220")] = true;
}

Next step is to test performing an upgrade of the contracts, test that we can send back tokens that exceed the limit and the new security roles.

@patitonar
Copy link
Contributor Author

@akolotov I found an issue while testing. This same issue affects security roles upgrades also v1 contracts.

Previouly I described an issue regarding onlyProxyOwner modifier #116 (comment)

The solution implemented to that issue, was to change the order of inheritance between OwnedUpgradeabilityProxy and EternalStorage.
However, that solution is not correct because it caused the loss of the state of the contract while upgrading the contracts in the last test (because of changing the structure of the store). Also, accessing to the proxyOwner value was not correct since it was read from the contract (initialized when deploying new contract version and was the same address) and not from proxy contract.
So I think this change should be reverted.

The proxyOwner variable is stored in a separte variable on the proxy contract and not on the EternalStorage, so I think is not possible to access that value from Bridge contracts.
Here are some documentation about EternalStorageProxy strategy that we are using.
https://blog.zeppelinos.org/proxy-patterns/
https://github.com/zeppelinos/labs/tree/master/upgradeability_using_eternal_storage

Given this, I think a possible solution is to introduce an admin role for Bridge contracts (similar to Ownable), that could be set on initialize method too.
And also update the method upgradeToV220 to include setting the address for the admin role.
The bad side of this approach, is that if proxyOwner is changed on the proxy contract, this admin role would have to also be updated manually.

Does this solution sound good? Any other option you can think of?

@akolotov
Copy link
Collaborator

akolotov commented Dec 12, 2018

Given this, I think a possible solution is to introduce an admin role for Bridge contracts (similar to Ownable), that could be set on initialize method too.
And also update the method upgradeToV220 to include setting the address for the admin role.
The bad side of this approach, is that if proxyOwner is changed on the proxy contract, this admin role would have to also be updated manually.

Honestly, I don't like it. Let me think more about this.

@akolotov
Copy link
Collaborator

akolotov commented Dec 13, 2018

@patitonar I have an idea. I did not tried this but what address will be assigned to this if you try to access this variable from a method in the bridge contract? If this address is the same as address of the proxy contract (not the bridge contract implementation), you can try to invoke address(this).proxyOwner() or use the call functionality explicitly.

Could you try this?

Copy link
Collaborator

@akolotov akolotov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

@patitonar
Copy link
Contributor Author

I performed the test with the new roles and the new limits functionality. Everything worked as expected. Only one issue was found regarding functionality (setExecutionMaxPerTx) and was fixed.

  • Bridge admin // HomeBridge.owner()

    • Can update parameter on bridge contract
    • Can not update parameter on validators contract
    • Can not upgrade bridge contract nor validators contract
    • Can not claim tokens
  • Validators admin // BridgeValidators.owner()

    • Can update parameter on validators contracts
    • Can not update parameter on bridge contract
    • Can not upgrade bridge contract nor validators contract
  • Upgradability admin // HomeBridgeEternalStorageProxy.proxyOwner()

    • Can upgrade bridge contract
    • Can claim tokens
    • Can fix assets above limits
  • Upgradability admin // BridgeValidatorsEternalStorageProxy.proxyOwner()

    • Can upgrade validators contract
  • State wasn't affected on contract upgrade.

  • About new limits functionality:

    • Sent a Tx above daily execution limit
    • Called fixAssetsAboveLimits(txHash, true) from Upgradability admin, which emitted UserRequestForSignature
    • out of limit amount was reduced
    • couldn't execute fixAssetsAboveLimits for the same txHash
    • UserRequestForSignature was processed by Bridge

For the contract upgrade, I had to call upgradeTo and then call method upgradeToV220. I wasn't able to call upgradeToAndCall.

The reason is that upgradeToV220 can be executed only by proxyOwner. On upgradeToAndCall when executing the method upgradeToV220 the msg.sender is the address of the proxy contract and not the upgradability admin address.

https://github.com/poanetwork/poa-bridge-contracts/blob/1f61c695df23b2a8f79d96e138c6bb5d8cd5b592/contracts/upgradeability/OwnedUpgradeabilityProxy.sol#L69-L72

Are the upgrade steps OK or should we refactor upgradeToV220 method to split logic between setters of new limits (just public method to be called with upgradeToAndCall) and logic for setting the new bridge owner (public onlyIfOwnerOfProxy method to be called after upgrade) ?

@akolotov
Copy link
Collaborator

Only one issue was found regarding functionality (setExecutionMaxPerTx) and was fixed.

Was the gist updated with the fix?

@patitonar
Copy link
Contributor Author

Was the gist updated with the fix?

Yes, both Gist files were updated. On Revisions section you can check the updates.

Home Bridge Gist: https://gist.github.com/patitonar/39ac4d337420864bc912123f72bc41f7
Foreign Bridge Gist: https://gist.github.com/patitonar/36a6e03c3ca13b912064b7742b5c3f5c

@akolotov
Copy link
Collaborator

Are the upgrade steps OK or should we refactor upgradeToV220 method to split logic between setters of new limits (just public method to be called with upgradeToAndCall) and logic for setting the new bridge owner (public onlyIfOwnerOfProxy method to be called after upgrade) ?

No, that's OK since the upgrade procedure is not trivial anyway:

  1. Upgrade contracts
  2. Set limits and owner for the bridge contracts on both sides
  3. Set a new owner for the validator contract
  4. Set a new upgrade admin for the validator contract

@akolotov
Copy link
Collaborator

So, let's finish changes for the contracts v1.x and distribute the changes to other modes of the bridge.

@akolotov
Copy link
Collaborator

@patitonar

Home Bridge Gist: https://gist.github.com/patitonar/39ac4d337420864bc912123f72bc41f7
Foreign Bridge Gist: https://gist.github.com/patitonar/36a6e03c3ca13b912064b7742b5c3f5c

During the upgrade we have found that

function upgradeToV220(address _newOwner) public onlyProxyOwner {

uses onlyProxyOwner that is not defined in the structure of used contracts. Most probably, onlyIfOwnerOfProxy should be used here.

Can you share why we did not catch this during the testing?

@patitonar
Copy link
Contributor Author

@akolotov As you mentioned, correct version of upgradeToV220 should use onlyIfOwnerOfProxy.

Home:

    function upgradeToV220(address _newOwner) public onlyIfOwnerOfProxy {
        require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
        setOwner(_newOwner);
        uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
        emit ExecutionDailyLimitChanged(1000000 ether);
        uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
        boolStorage[keccak256("isUpgradedToV220")] = true;
    }

Foreign:

    function upgradeToV220(address _newOwner) public onlyIfOwnerOfProxy {
        require(!boolStorage[keccak256(abi.encodePacked("isUpgradedToV220"))]);
        setOwner(_newOwner);
        uintStorage[keccak256(abi.encodePacked("maxPerTx"))] = 100000 ether;
        uintStorage[keccak256(abi.encodePacked("executionDailyLimit"))] = 1000000 ether;
        emit ExecutionDailyLimitChanged(1000000 ether);
        uintStorage[keccak256(abi.encodePacked("executionMaxPerTx"))] = 100000 ether;
        boolStorage[keccak256("isUpgradedToV220")] = true;
    }

These correct versions of upgradeToV220 were used while I performed the tests.

What I think happened is that, after fixing the issue that was found during the test (on method setExecutionMaxPerTx) when updating the file that was going to be used to update the Gist file, I may have accidentally overwritten upgradeToV220 with the older version of the method, that used onlyProxyOwner.

To avoid this situation next time I think we should:

  • Push changes to Gist file first, before running the test.
  • Review carefully the updates performed to Gist files.
  • If any fix was made after a test, update the Gist file, and run a new test with it.

I have fixed the methods upgradeToV220 on the Gist Files. Let me know which should be the next steps here.

@akolotov
Copy link
Collaborator

Ok, thanks! We already deployed the contracts to the ETH mainnet and the xDai chain.

@ghost ghost removed the in progress label Dec 21, 2018
@ghost ghost added the in progress label Dec 21, 2018
@patitonar
Copy link
Contributor Author

@akolotov Applied changes to other modes, updated deploy scripts, .env.example and README file

@akolotov
Copy link
Collaborator

akolotov commented Jan 3, 2019

The upgraded version of contracts was applied to the ETH mainnet and the xDai chain:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants