-
Notifications
You must be signed in to change notification settings - Fork 375
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
Script to deploy smart contract changes to a network via governance #4706
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.
Catching up right now, so please forgive any inaccurate statements.
If I understand correctly, the dilemma we are facing is that in master
, changes to linked libraries imply that all core contracts that rely on the changes would need to be explicitly upgraded as well?
The solution that you have outlined here is to make all linked libraries proxied, so that changes to libraries can be done via repointing the library proxy instead of having to upgrade the core contracts itself.
I couldn't tell from the PR how exactly you changed the core contracts to access the linked library via proxies vs. directly?
Additionally, would you mind elaborating again why you felt that upgrading all linked libraries was favorable in your opinion? Vs. Having core contracts being able to explicitly rely on possibly older versions of libraries. I assume it is because the invariant of having the source code at a commit represents the contract bytecode in totality on a network?
Correct, that is the implication. However, because changes to a library do not cause bytecode changes to any contracts that link that library, our current compatibility tool will not recognize this implication.
Exactly
There are two pieces to this. The first is to ensure changes to all contracts that link libraries, so that the compatibility tool flags them as needing to be upgraded. There is a sanity check in The second is to deploy the proxied libraries before any of the contracts that link them, and to explicitly link the contracts to those proxied libraries before deploying the contracts.
Correct, having core contracts rely on older versions of libraries breaks the invariant that the latest release branch represents exactly the contract code that is running on public networks. |
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.
forgot to click submit review!
import "../Proxy.sol"; | ||
|
||
/* solhint-disable no-empty-blocks */ | ||
contract AddressLinkedListProxy is Proxy {} |
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.
any reason why these need to be explicitly separate instead of just reusing Proxy
?
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 we wanted separate build artifacts for each. Happy to revisit this question outside the scope of this PR
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, this definitely made things easier when relying on Truffle for contract deployments. I guess going forward we'll be doing upgrade deployments somewhat differently (using this PR's script), but keeping with this convention might still be good to keep things organized, keep track of which contracts are proxied, + we still use Truffle for any dev deployments/new testnets.
* | ||
*/ | ||
|
||
class ContractDependencies { |
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 there are some libraries for this like https://github.com/ConsenSys/surya
not suggesting we use it at this stage but good to be aware of in the future
or similarly publishing these scripts as our own npm package for truffle projects
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.
Fortunately our migrations will fail if our hardcoded list of dependencies don't have complete coverage, so it's not as fragile as these sorts of things normally are.
// This makes essentially every contract dependent on Governance. | ||
console.log(`Transferring ownership of ${contractName}Proxy to Governance`) | ||
if (!dryRun) { | ||
await proxy._transferOwnership(addresses.get('Governance')) |
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 but we could pass in only the governance address to this function and fetch it once outside
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.
Did it this way to cover the future case where the governance address might change due to a new governance proxy being deployed
} | ||
// 2. Link dependencies. | ||
const Contract = await artifacts.require(contractName) | ||
await Promise.all(contractDependencies.map((d) => Contract.link(d, addresses.get(d)))) |
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.
does this mutate the artifacts?
do we know if we store the artifacts linked or unlinked?
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 I believe it does. They start off unlinked, and then they get linked 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.
On a related note, though not related to this PR directly, have we decided how we're going to be managing artifacts within the release process?
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.
Looks awesome! Left some comments, mostly nits.
import "../Proxy.sol"; | ||
|
||
/* solhint-disable no-empty-blocks */ | ||
contract AddressLinkedListProxy is Proxy {} |
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, this definitely made things easier when relying on Truffle for contract deployments. I guess going forward we'll be doing upgrade deployments somewhat differently (using this PR's script), but keeping with this convention might still be good to keep things organized, keep track of which contracts are proxied, + we still use Truffle for any dev deployments/new testnets.
@@ -7,7 +7,7 @@ import "../Attestations.sol"; | |||
* but mocks the implementations of the validator set getters. Otherwise we | |||
* couldn't test `request` with the current ganache local testnet. | |||
*/ | |||
contract TestAttestations is Attestations { | |||
contract AttestationsTest is Attestations { |
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.
Hmm, definitely a major nit, but to me XTest
sounds like "file containing tests for X", TestX
like "a version of X used for testing". Most of these files I think are the latter, so just wondering why the rename?
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.
Just to be consistent, happy to rename to TestX rather than XTest but would prefer not to block this PR on that
} | ||
// 2. Link dependencies. | ||
const Contract = await artifacts.require(contractName) | ||
await Promise.all(contractDependencies.map((d) => Contract.link(d, addresses.get(d)))) |
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.
On a related note, though not related to this PR directly, have we decided how we're going to be managing artifacts within the release process?
contract: `${contractName}Proxy`, | ||
function: '_setImplementation', | ||
args: [contract.address], | ||
value: '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.
Do we also want a human readable description like above?
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's sufficiently readable, the problem with the registry repointing is that the registry ID is not human readable
… governance" (#4706) into "oz-prerelease" (#5194) * Cherry pick 8a8b822 * Resolve merge conflicts from cherry-pick * Remove double-specification of ICeloVersionedContract Co-authored-by: Asa Oines <[email protected]>
… governance" (#4706) into "oz-prerelease" (#5194) * Cherry pick 8a8b822 * Resolve merge conflicts from cherry-pick * Remove double-specification of ICeloVersionedContract Co-authored-by: Asa Oines <[email protected]>
…elo-org#4706) ### Description This PR introduces the `make-release` script, which reads a backwards compatibility report against the latest release generated by `check-version`, deploys any changed contracts to the specified network, and outputs a JSON of the proposal that should be submitted to governance in order to promote the release. This script is part of a series of tools that are intended to make smart contract deployments as easy and safe as possible. These include: 1. *check-deployed*: Takes smart-contract source code and confirms that the specified network is running that code. Used to verify that the latest tagged release is in fact what has been released to the network. 2. *check-versions*: Takes two branches and checks that, given the smart contract versions and source code of branch A, branch B has correctly set the smart contract versions given the source code changes. Outputs a summary of all smart contract changes between branch A and B. Used to sanity check that the developers' understanding of all smart contract changes from A to B are fully understood, and to specify what contracts need to be upgraded in order to release the contracts in branch B onto a network that is currently running the contracts in branch A. 3. *make-release*: Takes the compatibility report output by *check-versions*, deploys the specified contracts, and outputs a governance proposal. Used to propose a release of the contracts in branch B onto a network that is currently running the contracts in branch A. 4. *verify-release*: Takes two branches and a proposal ID, creates a compatibility report with *check-versions*, and confirms that the on-chain governance proposal is the result of *make-release* on branches A and B. Used by CELO holders to verify contract upgrade proposals before voting. ## Other changes - Adds proxies for all linked libraries so that they can be upgraded in the future without having to upgrade contracts that link them. There is some special casing to redeploy all linked libraries with proxies in release 1, which should be removed after the release is made. - Adds versions to linked libraries - Flag rename in `check-versions` - Use 'Test' suffix to denote test contracts everywhere - Don't ignore linked list libraries in contract version check ### Tested First, spin up a ganache network with the contracts/0 release: ``` git checkout release/contracts/0 yarn devchain generate-tar .tmp/devchain.tar.gz && yarn devchain run-tar .tmp/devchain.tar.gz ``` Then, populate the initialization data for DowntimeSlasher: ``` echo '{"DowntimeSlasher": ["0x000000000000000000000000000000000000ce10", 10, 5, 20]}' > initialize_data.json ``` Finally, run the script: ``` yarn make-release -a release/contracts/0 -b asaj/make-release -n development -i initialize_data.json -p proposal.json -r report.json ``` ### Related issues -Part of broader work towards celo-org#4426 ### Backwards compatibility That's the goal!
Description
This PR introduces the
make-release
script, which reads a backwards compatibility report against the latest release generated bycheck-version
, deploys any changed contracts to the specified network, and outputs a JSON of the proposal that should be submitted to governance in order to promote the release.This script is part of a series of tools that are intended to make smart contract deployments as easy and safe as possible. These include:
Other changes
check-versions
Tested
First, spin up a ganache network with the contracts/0 release:
Then, populate the initialization data for DowntimeSlasher:
Finally, run the script:
Related issues
-Part of broader work towards #4426
Backwards compatibility
That's the goal!