(수정중)
// SPDX-License-Identifier: MIT
pragma solidity <0.7.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";
contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
struct AddressSlot {
address value;
}
// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) public {
require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success,) = _logic.delegatecall(
abi.encodeWithSignature("initialize()")
);
require(success, "Call failed");
}
// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback () external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}
// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r_slot := slot
}
}
}
contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address public upgrader;
uint256 public horsePower;
struct AddressSlot {
address value;
}
function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}
// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(address newImplementation, bytes memory data) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}
// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}
// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success,) = newImplementation.delegatecall(data);
require(success, "Call failed");
}
}
// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
AddressSlot storage r;
assembly {
r_slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
}
Would you be able to selfdestruct its engine and make the motorbike unusable?
Things that might be help:
- EIP-1967
- UUPS upgradable pattern
- Initializable contract
엔진을 부시자!
먼저 컨트랙트를 이해하기 위한 요소들부터 알아보자. Motorbike가 Proxy 컨트랙트고, Engine이 Implementation 컨트랙트이다. Engine에서 constructor를 사용할 수 없기 때문에 initializer modifier와 Motorbike 컨트랙트의 constructor를 이용해 가장 처음 실행되도록 했다.

Implementation 컨트랙트의 주소를 비롯해 변수들을 slot에 저장할 때, 순차적으로 하면 Storage Collision이 발생하기 때문에 keccak256으로 해시해서 나온 slot에 저장하자는 제안이다. 정확히는 아래와 같은 로직을 따른다.
bytes32 internal constant _IMPLEMENTATION_SLOT = bytes32(uint256(
keccak256('eip1967.proxy.implementation')) - 1
));

해당 주소가 컨트랙트인지 확인할 수 있는 함수다. 하지만 보안 상의 이유로 추천하지 않는다고 한다.
엔진을 부시려면 먼저 upgrader가 되어야 한다. 엔진 컨트랙트를 업그레이드 할 수 있는 권한을 가져와서 우리가 배포한 컨트랙트가 엔진 컨트랙트를 대체하도록 한다. 여기서 포인트는 대체한 컨트랙트에 selfdestruct() 함수를 넣어주는 것이다.