def _deploy_master_contract( ethereum_client: EthereumClient, deployer_account: LocalAccount, contract_fn: Callable[[Web3, Optional[str]], Contract], ) -> EthereumTxSent: """ Deploy master contract. Takes deployer_account (if unlocked in the node) or the deployer private key Safe with version > v1.1.1 doesn't need to be initialized as it already has a constructor :param ethereum_client: :param deployer_account: Ethereum account :param contract_fn: get contract function :return: deployed contract address """ safe_contract = contract_fn(ethereum_client.w3) constructor_tx = safe_contract.constructor().buildTransaction() tx_hash = ethereum_client.send_unsigned_transaction( constructor_tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=60) assert tx_receipt assert tx_receipt["status"] ethereum_tx_sent = EthereumTxSent(tx_hash, constructor_tx, tx_receipt["contractAddress"]) logger.info( "Deployed and initialized Safe Master Contract version=%s on address %s by %s", contract_fn( ethereum_client.w3, ethereum_tx_sent.contract_address).functions.VERSION().call(), ethereum_tx_sent.contract_address, deployer_account.address, ) return ethereum_tx_sent
def deploy_proxy_contract( self, deployer_account: LocalAccount, master_copy: str, initializer: bytes = b'', gas: Optional[int] = None, gas_price: Optional[int] = None) -> EthereumTxSent: """ Deploy proxy contract via ProxyFactory using `createProxy` function :param deployer_account: Ethereum account :param master_copy: Address the proxy will point at :param initializer: Initializer :param gas: Gas :param gas_price: Gas Price :return: EthereumTxSent """ proxy_factory_contract = self.get_contract() create_proxy_fn = proxy_factory_contract.functions.createProxy( master_copy, initializer) tx_parameters = {'from': deployer_account.address} contract_address = create_proxy_fn.call(tx_parameters) if gas_price is not None: tx_parameters['gasPrice'] = gas_price if gas is not None: tx_parameters['gas'] = gas tx = create_proxy_fn.buildTransaction(tx_parameters) # Auto estimation of gas does not work. We use a little more gas just in case tx['gas'] = tx['gas'] + 50000 tx_hash = self.ethereum_client.send_unsigned_transaction( tx, private_key=deployer_account.key) return EthereumTxSent(tx_hash, tx, contract_address)
def deploy_compatibility_fallback_handler( cls, ethereum_client: EthereumClient, deployer_account: LocalAccount) -> EthereumTxSent: """ Deploy Compatibility Fallback handler v1.3.0 :param ethereum_client: :param deployer_account: Ethereum account :return: deployed contract address """ contract = get_compatibility_fallback_handler_V1_3_0_contract( ethereum_client.w3) constructor_tx = contract.constructor().buildTransaction() tx_hash = ethereum_client.send_unsigned_transaction( constructor_tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=60) assert tx_receipt assert tx_receipt["status"] ethereum_tx_sent = EthereumTxSent(tx_hash, constructor_tx, tx_receipt["contractAddress"]) logger.info( "Deployed and initialized Compatibility Fallback Handler version=%s on address %s by %s", "1.3.0", ethereum_tx_sent.contract_address, deployer_account.address, ) return ethereum_tx_sent
def deploy_contract(ethereum_client: EthereumClient, deployer_account: LocalAccount) -> EthereumTxSent: """ Deploy proxy factory contract :param ethereum_client: :param deployer_account: Ethereum Account :return: deployed contract address """ contract = get_multi_send_contract(ethereum_client.w3) tx = contract.constructor().buildTransaction( {"from": deployer_account.address}) tx_hash = ethereum_client.send_unsigned_transaction( tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=120) assert tx_receipt assert tx_receipt["status"] contract_address = tx_receipt["contractAddress"] logger.info( "Deployed and initialized Proxy Factory Contract=%s by %s", contract_address, deployer_account.address, ) return EthereumTxSent(tx_hash, tx, contract_address)
def deploy_master_contract( ethereum_client: EthereumClient, deployer_account: LocalAccount) -> EthereumTxSent: """ Deploy master contract. Takes deployer_account (if unlocked in the node) or the deployer private key Safe with version > v1.1.1 doesn't need to be initialized as it already has a constructor :param ethereum_client: :param deployer_account: Ethereum account :return: deployed contract address """ safe_contract = get_safe_contract(ethereum_client.w3) constructor_tx = safe_contract.constructor().buildTransaction() tx_hash = ethereum_client.send_unsigned_transaction( constructor_tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=60) assert tx_receipt assert tx_receipt['status'] ethereum_tx_sent = EthereumTxSent(tx_hash, constructor_tx, tx_receipt['contractAddress']) logger.info("Deployed and initialized Safe Master Contract=%s by %s", ethereum_tx_sent.contract_address, deployer_account.address) return ethereum_tx_sent
def create(ethereum_client: EthereumClient, deployer_account: LocalAccount, master_copy_address: str, owners: List[str], threshold: int, fallback_handler: str = NULL_ADDRESS, proxy_factory_address: Optional[str] = None, payment_token: str = NULL_ADDRESS, payment: int = 0, payment_receiver: str = NULL_ADDRESS) -> EthereumTxSent: """ Deploy new Safe proxy pointing to the specified `master_copy` address and configured with the provided `owners` and `threshold`. By default, payment for the deployer of the tx will be `0`. If `proxy_factory_address` is set deployment will be done using the proxy factory instead of calling the `constructor` of a new `DelegatedProxy` Using `proxy_factory_address` is recommended, as it takes less gas. (Testing with `Ganache` and 1 owner 261534 without proxy vs 229022 with Proxy) """ assert owners, 'At least one owner must be set' assert threshold >= len(owners), 'Threshold=%d must be >= %d' % ( threshold, len(owners)) initializer = get_safe_contract( ethereum_client.w3, NULL_ADDRESS ).functions.setup( owners, threshold, NULL_ADDRESS, # Contract address for optional delegate call b'', # Data payload for optional delegate call fallback_handler, # Handler for fallback calls to this contract, payment_token, payment, payment_receiver).buildTransaction({ 'gas': 1, 'gasPrice': 1 })['data'] if proxy_factory_address: proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client) return proxy_factory.deploy_proxy_contract(deployer_account, master_copy_address, initializer=initializer) proxy_contract = get_delegate_constructor_proxy_contract( ethereum_client.w3) tx = proxy_contract.constructor(master_copy_address, initializer).buildTransaction( {'from': deployer_account.address}) tx['gas'] = tx['gas'] * 100000 tx_hash = ethereum_client.send_unsigned_transaction( tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=60) assert tx_receipt.status contract_address = tx_receipt.contractAddress return EthereumTxSent(tx_hash, tx, contract_address)
def send_multisig_tx(self, to: str, value: int, data: bytes, operation: int, safe_tx_gas: int, base_gas: int, gas_price: int, gas_token: str, refund_receiver: str, signatures: bytes, tx_sender_private_key: str, tx_gas=None, tx_gas_price=None, block_identifier: Optional[BlockIdentifier] = 'latest') -> EthereumTxSent: """ Build and send Safe tx :param to: :param value: :param data: :param operation: :param safe_tx_gas: :param base_gas: :param gas_price: :param gas_token: :param refund_receiver: :param signatures: :param tx_sender_private_key: :param tx_gas: Gas for the external tx. If not, `(safe_tx_gas + data_gas) * 2` will be used :param tx_gas_price: Gas price of the external tx. If not, `gas_price` will be used :param block_identifier: :return: Tuple(tx_hash, tx) :raises: InvalidMultisigTx: If user tx cannot go through the Safe """ safe_tx = self.build_multisig_tx(to, value, data, operation, safe_tx_gas, base_gas, gas_price, gas_token, refund_receiver, signatures) tx_sender_address = Account.from_key(tx_sender_private_key).address safe_tx.call(tx_sender_address=tx_sender_address, block_identifier=block_identifier) tx_hash, tx = safe_tx.execute(tx_sender_private_key=tx_sender_private_key, tx_gas=tx_gas, tx_gas_price=tx_gas_price, block_identifier=block_identifier) return EthereumTxSent(tx_hash, tx, None)
def _deploy_proxy_factory_contract(ethereum_client: EthereumClient, deployer_account: LocalAccount, contract: Contract) -> EthereumTxSent: tx = contract.constructor().buildTransaction({'from': deployer_account.address}) tx_hash = ethereum_client.send_unsigned_transaction(tx, private_key=deployer_account.key) tx_receipt = ethereum_client.get_transaction_receipt(tx_hash, timeout=120) assert tx_receipt assert tx_receipt['status'] contract_address = tx_receipt['contractAddress'] logger.info("Deployed and initialized Proxy Factory Contract=%s by %s", contract_address, deployer_account.address) return EthereumTxSent(tx_hash, tx, contract_address)
def deploy_proxy_contract_with_nonce( self, deployer_account: LocalAccount, master_copy: ChecksumAddress, initializer: bytes, salt_nonce: int, gas: Optional[int] = None, gas_price: Optional[int] = None, nonce: Optional[int] = None, ) -> EthereumTxSent: """ Deploy proxy contract via Proxy Factory using `createProxyWithNonce` (create2) :param deployer_account: Ethereum account :param master_copy: Address the proxy will point at :param initializer: Data for safe creation :param salt_nonce: Uint256 for `create2` salt :param gas: Gas :param gas_price: Gas Price :param nonce: Nonce :return: Tuple(tx-hash, tx, deployed contract address) """ proxy_factory_contract = self.get_contract() create_proxy_fn = proxy_factory_contract.functions.createProxyWithNonce( master_copy, initializer, salt_nonce) tx_parameters = {"from": deployer_account.address} contract_address = create_proxy_fn.call(tx_parameters) if gas_price is not None: tx_parameters["gasPrice"] = gas_price if gas is not None: tx_parameters["gas"] = gas if nonce is not None: tx_parameters["nonce"] = nonce tx = create_proxy_fn.buildTransaction(tx_parameters) # Auto estimation of gas does not work. We use a little more gas just in case tx["gas"] = tx["gas"] + 50000 tx_hash = self.ethereum_client.send_unsigned_transaction( tx, private_key=deployer_account.key) return EthereumTxSent(tx_hash, tx, contract_address)