diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 8249b7287b72..d7dc813a0893 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -44,6 +44,7 @@ import { CeloRegistry } from "src/celo/CeloRegistry.sol"; import { FeeHandler } from "src/celo/FeeHandler.sol"; import { MentoFeeHandlerSeller } from "src/celo/MentoFeeHandlerSeller.sol"; import { UniswapFeeHandlerSeller } from "src/celo/UniswapFeeHandlerSeller.sol"; +import { ICeloProxy } from "src/celo/ICeloProxy.sol"; import { SortedOracles } from "src/celo/stability/SortedOracles.sol"; import { FeeCurrencyDirectory } from "src/celo/FeeCurrencyDirectory.sol"; import { FeeCurrency } from "src/celo/testing/FeeCurrency.sol"; @@ -54,6 +55,8 @@ interface IInitializable { function initialize(address _addr) external; } + + struct L1Dependencies { address payable l1CrossDomainMessengerProxy; address payable l1StandardBridgeProxy; @@ -71,9 +74,10 @@ contract L2Genesis is Deployer { using ForkUtils for Fork; using OutputModeUtils for OutputMode; + address constant defaultOwner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // TODO this is DEV_ACCOUNT_FUND_AMT uint256 public constant PRECOMPILE_COUNT = 256; - uint80 internal constant DEV_ACCOUNT_FUND_AMT = 10_000 ether; + uint80 internal constant DEV_ACCOUNT_FUND_AMT = 70_000 ether; /// @notice Default Anvil dev accounts. Only funded if `cfg.fundDevAccounts == true`. /// Also known as "test test test test test test test test test test test junk" mnemonic accounts, @@ -675,6 +679,8 @@ contract L2Genesis is Deployer { console.log("Funding dev account %s with %s ETH", devAccounts[i], DEV_ACCOUNT_FUND_AMT / 1e18); vm.deal(devAccounts[i], DEV_ACCOUNT_FUND_AMT); } + // fund an address that'd then be used to fund the celoUnreleasedTreasury + vm.deal(devAccounts[1], 400_000_000 ether); } ///@notice Sets all proxies and implementations for Celo contracts @@ -683,6 +689,7 @@ contract L2Genesis is Deployer { setCeloRegistry(); setCeloGoldToken(); + setCeloTreasury(); setCeloFeeHandler(); setCeloMentoFeeHandlerSeller(); setCeloUniswapFeeHandlerSeller(); @@ -690,71 +697,106 @@ contract L2Genesis is Deployer { setFeeCurrencyDirectory(); } + /// @notice Sets up a proxy for the given impl address function _setupProxy(address addr, address impl) internal returns (address) { - bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy"); + // bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy"); + bytes memory code = vm.getDeployedCode("CeloProxy.sol:CeloProxy"); vm.etch(addr, code); - EIP1967Helper.setAdmin(addr, Predeploys.PROXY_ADMIN); + + console.log("Owner of Celo proxy is:", ICeloProxy(addr)._getOwner()); + vm.startPrank(ICeloProxy(addr)._getOwner()); + ICeloProxy(addr)._transferOwnership(defaultOwner); + + vm.stopPrank(); + // EIP1967Helper.setAdmin(addr, Predeploys.PROXY_ADMIN); console.log("Setting proxy %s with implementation: %s", addr, impl); - EIP1967Helper.setImplementation(addr, impl); return addr; } function setCeloRegistry() internal { - CeloRegistry kontract = new CeloRegistry({ test: false }); + // deployCodeTo("CeloRegistry.sol", abi.encode(false), REGISTRY_ADDRESS); + + // address REGISTRY_ADDRESS = address(0x000000000000000000000000000000000000CE11); + // bytes memory code = vm.getDeployedCode("CeloRegistry.sol:CeloRegistry"); + // vm.etch(REGISTRY_ADDRESS, code); + + // CeloRegistry contract_ = new CeloRegistry({ test: false }); address precompile = CeloPredeploys.CELO_REGISTRY; + string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); + // console.log("Deploying %s implementation at: %s", cname, address(contract_)); + + // vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(0)); + + // CeloRegistry registry = CeloRegistry(precompile); + // address celoOwner = registry.owner(); + // vm.startPrank(address(celoOwner)); + // registry._transferOwnership(defaultOwner); + // vm.stopPrank(); + + // console.log("CeloRegistry owner: %s", celoOwner); + // revert("vert on puporse"); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); } function setCeloGoldToken() internal { - GoldToken kontract = new GoldToken({ test: false }); + GoldToken contract_ = new GoldToken({ test: false }); address precompile = CeloPredeploys.GOLD_TOKEN; string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); + console.log("Deploying %s implementation at: %s", cname, address(contract_)); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); + vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(contract_)); + } + + function setCeloTreasury() internal { + // GoldToken contract_ = new GoldToken({ test: false }); + + address precompile = CeloPredeploys.TREASURY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(0)); + + // vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(0)); } function setCeloFeeHandler() internal { - FeeHandler kontract = new FeeHandler({ test: false }); + FeeHandler contract_ = new FeeHandler({ test: false }); address precompile = CeloPredeploys.FEE_HANDLER; string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); + console.log("Deploying %s implementation at: %s", cname, address(contract_)); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); + vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(contract_)); } function setCeloMentoFeeHandlerSeller() internal { - MentoFeeHandlerSeller kontract = new MentoFeeHandlerSeller({ test: false }); + MentoFeeHandlerSeller contract_ = new MentoFeeHandlerSeller({ test: false }); address precompile = CeloPredeploys.MENTO_FEE_HANDLER_SELLER; string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); + console.log("Deploying %s implementation at: %s", cname, address(contract_)); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); + vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(contract_)); } function setCeloUniswapFeeHandlerSeller() internal { - UniswapFeeHandlerSeller kontract = new UniswapFeeHandlerSeller({ test: false }); + UniswapFeeHandlerSeller contract_ = new UniswapFeeHandlerSeller({ test: false }); address precompile = CeloPredeploys.UNISWAP_FEE_HANDLER_SELLER; string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); + console.log("Deploying %s implementation at: %s", cname, address(contract_)); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); + vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(contract_)); } function setFeeCurrencyDirectory() internal { @@ -767,17 +809,17 @@ contract L2Genesis is Deployer { vm.resetNonce(address(feeCurrencyDirectory)); _setupProxy(precompile, address(feeCurrencyDirectory)); - vm.startPrank(devAccounts[0]); - FeeCurrencyDirectory(precompile).initialize(); - vm.stopPrank(); + // vm.startPrank(devAccounts[0]); + // FeeCurrencyDirectory(precompile).initialize(); + // vm.stopPrank(); } function setCeloFeeCurrency() internal { - FeeCurrency kontract = new FeeCurrency({ name_: "Test", symbol_: "TST" }); + FeeCurrency contract_ = new FeeCurrency({ name_: "Test", symbol_: "TST" }); address precompile = CeloPredeploys.FEE_CURRENCY; string memory cname = CeloPredeploys.getName(precompile); - console.log("Deploying %s implementation at: %s", cname, address(kontract)); - vm.resetNonce(address(kontract)); - _setupProxy(precompile, address(kontract)); + console.log("Deploying %s implementation at: %s", cname, address(contract_)); + vm.resetNonce(address(contract_)); + _setupProxy(precompile, address(contract_)); } } diff --git a/packages/contracts-bedrock/src/celo/Address.sol b/packages/contracts-bedrock/src/celo/Address.sol new file mode 100644 index 000000000000..d69d13bdb12e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/Address.sol @@ -0,0 +1,72 @@ +pragma solidity ^0.5.5; + +// copy of import "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != accountHash && codehash != 0x0); + } + + /** + * @dev Converts an `address` into `address payable`. Note that this is + * simply a type cast: the actual underlying value is not changed. + * + * _Available since v2.4.0._ + */ + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + * + * _Available since v2.4.0._ + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call.value(amount)(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloPredeploys.sol b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol index 91dc4cd44189..a1752eb0ed1a 100644 --- a/packages/contracts-bedrock/src/celo/CeloPredeploys.sol +++ b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol @@ -16,6 +16,7 @@ library CeloPredeploys { address internal constant FEE_CURRENCY = 0x4200000000000000000000000000000000001022; address internal constant FEE_CURRENCY_DIRECTORY = 0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF; address internal constant cUSD = 0x765DE816845861e75A25fCA122bb6898B8B1282a; + address internal constant TREASURY = 0xB76D502Ad168F9D545661ea628179878DcA92FD5; /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { @@ -31,6 +32,7 @@ library CeloPredeploys { if (_addr == FEE_CURRENCY) return "FeeCurrency"; if (_addr == FEE_CURRENCY_DIRECTORY) return "FeeCurrencyDirectory"; if (_addr == cUSD) return "cUSD"; + if (_addr == TREASURY) return "TREASURY"; revert("Predeploys: unnamed predeploy"); } diff --git a/packages/contracts-bedrock/src/celo/CeloProxy.sol b/packages/contracts-bedrock/src/celo/CeloProxy.sol new file mode 100644 index 000000000000..dbbaefcb2e96 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloProxy.sol @@ -0,0 +1,162 @@ +pragma solidity ^0.5.13; +/* solhint-disable no-inline-assembly, no-complex-fallback, avoid-low-level-calls */ + +// import "@openzeppelin/contracts/utils/Address.sol"; +import "./Address.sol"; + +/** + * @title A Proxy utilizing the Unstructured Storage pattern. + */ +contract CeloProxy { + // Used to store the address of the owner. + bytes32 private constant OWNER_POSITION = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); + // Used to store the address of the implementation contract. + bytes32 private constant IMPLEMENTATION_POSITION = + bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); + + event OwnerSet(address indexed owner); + event ImplementationSet(address indexed implementation); + + /** + * @notice Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == _getOwner(), "sender was not owner"); + _; + } + + constructor() public { + _setOwner(msg.sender); + } + + /** + * @notice Delegates calls to the implementation contract. + */ + function() external payable { + bytes32 implementationPosition = IMPLEMENTATION_POSITION; + + address implementationAddress; + + // Load the address of the implementation contract from an explicit storage slot. + assembly { + implementationAddress := sload(implementationPosition) + } + + // Avoid checking if address is a contract or executing delegated call when + // implementation address is 0x0 + require(implementationAddress != address(0), "No Implementation set"); + require(Address.isContract(implementationAddress), "Invalid contract address"); + + assembly { + // Extract the position of the transaction data (i.e. function ID and arguments). + let newCallDataPosition := mload(0x40) + mstore(0x40, add(newCallDataPosition, calldatasize)) + calldatacopy(newCallDataPosition, 0, calldatasize) + + // Call the smart contract at `implementationAddress` in the context of the proxy contract, + // with the same msg.sender and value. + let delegatecallSuccess := delegatecall( + gas, + implementationAddress, + newCallDataPosition, + calldatasize, + 0, + 0 + ) + + // Copy the return value of the call so it can be returned. + let returnDataSize := returndatasize + let returnDataPosition := mload(0x40) + mstore(0x40, add(returnDataPosition, returnDataSize)) + returndatacopy(returnDataPosition, 0, returnDataSize) + + // Revert or return depending on whether or not the call was successful. + switch delegatecallSuccess + case 0 { + revert(returnDataPosition, returnDataSize) + } + default { + return(returnDataPosition, returnDataSize) + } + } + } + + /** + * @notice Transfers ownership of Proxy to a new owner. + * @param newOwner Address of the new owner account. + */ + function _transferOwnership(address newOwner) external onlyOwner { + _setOwner(newOwner); + } + + /** + * @notice Sets the address of the implementation contract and calls into it. + * @param implementation Address of the new target contract. + * @param callbackData The abi-encoded function call to perform in the implementation + * contract. + * @dev Throws if the initialization callback fails. + * @dev If the target contract does not need initialization, use + * setImplementation instead. + */ + function _setAndInitializeImplementation( + address implementation, + bytes calldata callbackData + ) external payable onlyOwner { + _setImplementation(implementation); + bool success; + bytes memory returnValue; + (success, returnValue) = implementation.delegatecall(callbackData); + require(success, "initialization callback failed"); + } + + /** + * @notice Returns the implementation address. + */ + function _getImplementation() external view returns (address implementation) { + bytes32 implementationPosition = IMPLEMENTATION_POSITION; + // Load the address of the implementation contract from an explicit storage slot. + assembly { + implementation := sload(implementationPosition) + } + } + + /** + * @notice Sets the address of the implementation contract. + * @param implementation Address of the new target contract. + * @dev If the target contract needs to be initialized, call + * setAndInitializeImplementation instead. + */ + function _setImplementation(address implementation) public onlyOwner { + bytes32 implementationPosition = IMPLEMENTATION_POSITION; + + require(Address.isContract(implementation), "Invalid contract address"); + + // Store the address of the implementation contract in an explicit storage slot. + assembly { + sstore(implementationPosition, implementation) + } + + emit ImplementationSet(implementation); + } + + /** + * @notice Returns the Proxy owner's address. + */ + function _getOwner() public view returns (address owner) { + bytes32 position = OWNER_POSITION; + // Load the address of the contract owner from an explicit storage slot. + assembly { + owner := sload(position) + } + } + + function _setOwner(address newOwner) internal { + require(newOwner != address(0), "owner cannot be 0"); + bytes32 position = OWNER_POSITION; + // Store the address of the contract owner in an explicit storage slot. + assembly { + sstore(position, newOwner) + } + emit OwnerSet(newOwner); + } +} diff --git a/packages/contracts-bedrock/src/celo/ICeloProxy.sol b/packages/contracts-bedrock/src/celo/ICeloProxy.sol new file mode 100644 index 000000000000..7bd85e5af719 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/ICeloProxy.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface ICeloProxy { + // function() external payable; + function _setAndInitializeImplementation( + address implementation, + bytes calldata callbackData + ) external payable; + function _transferOwnership(address newOwner) external; + function _getImplementation() external view returns (address implementation); + function _getOwner() external view returns (address owner); +}