forked from forta-network/forta-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
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
Add a variant of the Vesting wallet for updating the beneficiary #1
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5f3cef7
add a variant of the Vesting wallet for updating the beneficiary
Amxx 0e49e38
up
Amxx 2bc8040
Update contracts/vesting/VestingWalletRecovery.sol
Amxx ea128e2
transitory upgrade
Amxx 42adfd9
up
Amxx 83f2a50
test recovering from all 3 versions
Amxx f05d879
Apply suggestions from code review
Amxx 6e90a5b
update
Amxx 89d2141
Update VestingWalletRecoveryLight.sol
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
// See Forta Network License: https://github.com/forta-network/forta-contracts/blob/master/LICENSE.md | ||
|
||
pragma solidity ^0.8.9; | ||
|
||
import "@openzeppelin/contracts/utils/Address.sol"; | ||
import "@openzeppelin/contracts/utils/StorageSlot.sol"; | ||
|
||
/** | ||
* This contract is designed for recovery in case the beneficiary was lost. | ||
* | ||
* This contract replicates a storage layout that is compatible with VestingWallet, VestingWalletV1 and | ||
* VestingWalletV2. | ||
*/ | ||
contract VestingWalletRecoveryLight { | ||
/// Storage | ||
// Initializable | ||
uint8 private _initialized; | ||
bool private _initializing; | ||
// ContextUpgradeable | ||
uint256[50] private __gap_1; | ||
// OwnableUpgradeable | ||
address private _owner; | ||
uint256[49] private __gap_2; | ||
// UUPSUpgradeable | ||
uint256[50] private __gap_3; | ||
// ERC1967UpgradeUpgradeable | ||
uint256[50] private __gap_4; | ||
// VestingWallet / VestingWalletV1 / VestingWalletV2 | ||
mapping (address => uint256) private _released; | ||
address private _beneficiary; | ||
uint256 private _start; | ||
uint256 private _cliff; | ||
uint256 private _duration; | ||
// VestingWalletV1 / VestingWalletV2 | ||
uint256[45] private __gap_5; | ||
// VestingWalletV2 | ||
uint256 private historicalBalanceMin; | ||
uint256[45] private __gap_6; | ||
|
||
/// Constants and Events | ||
// ERC1967UpgradeUpgradeable | ||
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
event Upgraded(address indexed implementation); | ||
event BeneficiaryUpdate(address newBeneficiary); | ||
|
||
/// @dev The `previousImplementation` address MUST correspond to the real previous implementation. | ||
/// Otherwise, upgrading to this implementation contract and calling this function will leave the new implementation uninitialized. | ||
function changeBeneficiaryAndRollbackTo(address newBeneficiary, address previousImplementation) external { | ||
// restrict to owner, in case the upgrade misses the "andCall" part. | ||
require(msg.sender == _owner); | ||
|
||
// change beneficiary | ||
_beneficiary = newBeneficiary; | ||
emit BeneficiaryUpdate(newBeneficiary); | ||
|
||
// ERC1967Upgrade._setImplementation | ||
require(Address.isContract(previousImplementation), "ERC1967: new implementation is not a contract"); | ||
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = previousImplementation; | ||
emit Upgraded(previousImplementation); | ||
} | ||
|
||
function proxiableUUID() external pure returns (bytes32) { | ||
return _IMPLEMENTATION_SLOT; | ||
} | ||
|
||
function upgradeTo(address newImplementation) external { | ||
require(msg.sender == _owner); | ||
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; | ||
} | ||
|
||
function upgradeToAndCall(address newImplementation, bytes memory data) external { | ||
require(msg.sender == _owner); | ||
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; | ||
if (data.length > 0) { | ||
Address.functionDelegateCall(newImplementation, data); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
const hre = require('hardhat'); | ||
const { ethers } = hre; | ||
const { expect } = require('chai'); | ||
const { prepare, deployUpgradeable, performUpgrade } = require('../fixture'); | ||
const utils = require('../../scripts/utils'); | ||
|
||
const allocation = { | ||
start: utils.dateToTimestamp('2021-09-01T00:00:00Z'), | ||
cliff: utils.durationToSeconds('1 year'), | ||
duration: utils.durationToSeconds('4 years'), | ||
}; | ||
|
||
describe('Vesting recovery', function () { | ||
prepare(); | ||
|
||
for (const { contractName, constructorArgs } of [ | ||
{ contractName: 'VestingWallet' }, | ||
{ contractName: 'VestingWalletV1' }, | ||
{ contractName: 'VestingWalletV2', constructorArgs: Array(4).fill(ethers.constants.AddressZero) }, | ||
]) | ||
describe(contractName, function () { | ||
beforeEach(async function () { | ||
allocation.beneficiary = this.accounts.user1.address; | ||
allocation.newBeneficiary = this.accounts.user2.address; | ||
allocation.owner = this.accounts.admin.address; | ||
|
||
this.vesting = await deployUpgradeable( | ||
hre, | ||
contractName, | ||
'uups', | ||
[allocation.beneficiary, allocation.owner, allocation.start, allocation.cliff, allocation.duration], | ||
{ unsafeAllow: 'delegatecall', constructorArgs } | ||
); | ||
}); | ||
|
||
describe('recover beneficiary', function () { | ||
beforeEach(async function () { | ||
await Promise.all([this.vesting.start(), this.vesting.cliff(), this.vesting.duration(), this.vesting.beneficiary(), this.vesting.owner()]).then( | ||
([start, cliff, duration, beneficiary, owner]) => { | ||
expect(start).to.be.equal(allocation.start); | ||
expect(cliff).to.be.equal(allocation.cliff); | ||
expect(duration).to.be.equal(allocation.duration); | ||
expect(beneficiary).to.be.equal(allocation.beneficiary); | ||
expect(owner).to.be.equal(allocation.owner); | ||
} | ||
); | ||
}); | ||
|
||
it('perform upgrade', async function () { | ||
const implementation = await hre.upgrades.erc1967.getImplementationAddress(this.vesting.address); | ||
await performUpgrade(hre, this.vesting, 'VestingWalletRecoveryLight', { | ||
call: { fn: 'changeBeneficiaryAndRollbackTo', args: [allocation.newBeneficiary, implementation] }, | ||
unsafeAllow: 'delegatecall' | ||
}); | ||
}); | ||
|
||
afterEach(async function () { | ||
await Promise.all([this.vesting.start(), this.vesting.cliff(), this.vesting.duration(), this.vesting.beneficiary(), this.vesting.owner()]).then( | ||
([start, cliff, duration, beneficiary, owner]) => { | ||
expect(start).to.be.equal(allocation.start); | ||
expect(cliff).to.be.equal(allocation.cliff); | ||
expect(duration).to.be.equal(allocation.duration); | ||
expect(beneficiary).to.be.equal(allocation.newBeneficiary); | ||
expect(owner).to.be.equal(allocation.owner); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Without specifying, it looks weird that we're checking ownership but not emitting the event.