-
Notifications
You must be signed in to change notification settings - Fork 939
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes: #787 ## Summary This PR adds a general migration contract that takes address of the Safe, SafeL2 and fallback handler contracts during deployment. The contract allows Safe to update the Singleton at `address(0)`. - Uses error strings rather than error types because Solidity version 0.7.6 doesn't support it. As of now tests cover below migration paths: - 1.3.0 to 1.5.0 - 1.3.0 to 1.4.1 - 14.1 to 1.5.0 See `SafeMigration.spec.ts` to see how tests are organised. Do share if you any thoughts to better run same tests on different migration paths. The migration contract stores address of the Safe singletons and fallback handler rather than using code hash and requiring the user to provide singleton address as described in the issue. The reason being as follows: Checking codehash of the target singleton means user has to provide the address of the target singleton. Also, checking code hash has higher gas costs. The only argument for using code hash for upgrades is that it also allows unofficial singletons to be used for migration using official migration contract. But, users/projects can also deploy their own version of migration contract by providing singleton addresses in the constructor and have similar security guarantees as the official migration contract. ## Changes in PR - Create a general migration contract which is not tightly bound to any specific Safe version - Update tests - Remove other migration contract as this PR supersedes it Unlike `Safe150Migration.sol`, this new contract does not check if slot(0) of the contract stores an address having some non-empty code. I think this check is not need because this contract is not intended to be used in general by other proxy contracts and checking slot(0) value is only a partially correct way. Would like to know thought of others. --------- Co-authored-by: Nicholas Rodrigues Lordello <[email protected]> Co-authored-by: Shebin John <[email protected]> Co-authored-by: Mikhail <[email protected]>
- Loading branch information
1 parent
8f80a83
commit b6692f9
Showing
10 changed files
with
503 additions
and
866 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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,120 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
import {SafeStorage} from "../libraries/SafeStorage.sol"; | ||
import {ISafe} from "../interfaces/ISafe.sol"; | ||
|
||
/** | ||
* @title Migration Contract for Safe Upgrade | ||
* @notice This is a generic contract that facilitates Safe and SafeL2 proxy contracts to migrate their singleton address. | ||
* The supported target Safe version is immutable and set in the constructor during the deployment of the contract. | ||
* This contract also supports migration with fallback handler update. | ||
* @author @safe-global/safe-protocol | ||
* @dev IMPORTANT: The library is intended to be used with the Safe standard proxy that stores the singleton address | ||
* at the storage slot 0. Use at your own risk with custom proxy implementations. The contract will allow invocations | ||
* to the migration functions only via delegatecall. | ||
*/ | ||
contract SafeMigration is SafeStorage { | ||
/** | ||
* @notice Address of this contract | ||
*/ | ||
address public immutable MIGRATION_SINGLETON; | ||
/** | ||
* @notice Address of the Safe Singleton implementation | ||
*/ | ||
address public immutable SAFE_SINGLETON; | ||
/** | ||
* @notice Address of the Safe Singleton (L2) implementation | ||
*/ | ||
address public immutable SAFE_L2_SINGLETON; | ||
/** | ||
* @notice Addresss of the Fallback Handler | ||
*/ | ||
address public immutable SAFE_FALLBACK_HANDLER; | ||
|
||
/** | ||
* @notice Event indicating a change of a singleton address. Named master copy here for legacy reasons. | ||
* @param singleton New master copy address | ||
*/ | ||
event ChangedMasterCopy(address singleton); | ||
|
||
/** | ||
* @notice Modifier to make a function callable via delegatecall only. | ||
* If the function is called via a regular call, it will revert. | ||
*/ | ||
modifier onlyDelegateCall() { | ||
require(address(this) != MIGRATION_SINGLETON, "Migration should only be called via delegatecall"); | ||
_; | ||
} | ||
|
||
/** | ||
* @notice Constructor | ||
* @param safeSingleton Address of the Safe Singleton implementation | ||
* @param safeL2Singleton Address of the SafeL2 Singleton implementation | ||
* @param fallbackHandler Address of the fallback handler implementation | ||
*/ | ||
constructor(address safeSingleton, address safeL2Singleton, address fallbackHandler) { | ||
MIGRATION_SINGLETON = address(this); | ||
|
||
require(hasCode(safeSingleton), "Safe Singleton is not deployed"); | ||
require(hasCode(safeL2Singleton), "Safe Singleton (L2) is not deployed"); | ||
require(hasCode(fallbackHandler), "fallback handler is not deployed"); | ||
|
||
SAFE_SINGLETON = safeSingleton; | ||
SAFE_L2_SINGLETON = safeL2Singleton; | ||
SAFE_FALLBACK_HANDLER = fallbackHandler; | ||
} | ||
|
||
/** | ||
* @notice Migrate the Safe contract to a new Safe Singleton implementation. | ||
*/ | ||
function migrateSingleton() public onlyDelegateCall { | ||
singleton = SAFE_SINGLETON; | ||
emit ChangedMasterCopy(SAFE_SINGLETON); | ||
} | ||
|
||
/** | ||
* @notice Migrate to Safe Singleton and set the fallback handler. This function is intended to be used when migrating | ||
* a Safe to a version which also requires updating fallback handler. | ||
*/ | ||
function migrateWithFallbackHandler() public onlyDelegateCall { | ||
migrateSingleton(); | ||
ISafe(address(this)).setFallbackHandler(SAFE_FALLBACK_HANDLER); | ||
} | ||
|
||
/** | ||
* @notice Migrate the Safe contract to a new Safe Singleton (L2) implementation. | ||
*/ | ||
function migrateL2Singleton() public onlyDelegateCall { | ||
singleton = SAFE_L2_SINGLETON; | ||
emit ChangedMasterCopy(SAFE_L2_SINGLETON); | ||
} | ||
|
||
/** | ||
* @notice Migrate to Safe Singleton (L2) and set the fallback handler. This function is intended to be used when migrating | ||
* a Safe to a version which also requires updating fallback handler. | ||
*/ | ||
function migrateL2WithFallbackHandler() public onlyDelegateCall { | ||
migrateL2Singleton(); | ||
ISafe(address(this)).setFallbackHandler(SAFE_FALLBACK_HANDLER); | ||
} | ||
|
||
/** | ||
* @notice Checks whether an account has code. | ||
* @param account The address of the account to be checked. | ||
* @return A boolean value indicating whether the address has code (true) or not (false). | ||
* @dev This function relies on the `extcodesize` assembly opcode to determine whether an address has code. | ||
* It does not reliably determine whether or not an address is a smart contract or an EOA. | ||
*/ | ||
function hasCode(address account) internal view returns (bool) { | ||
uint256 size; | ||
/* solhint-disable no-inline-assembly */ | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
size := extcodesize(account) | ||
} | ||
/* solhint-enable no-inline-assembly */ | ||
|
||
return size > 0; | ||
} | ||
} |
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
Oops, something went wrong.