class DSAuth(Contract): abi = Contract._load_abi(__name__, 'abi/DSAuth.abi') bin = Contract._load_bin(__name__, 'abi/DSAuth.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @staticmethod def deploy(web3: Web3): return DSAuth(web3=web3, address=Contract._deploy(web3, DSAuth.abi, DSAuth.bin, [])) def get_owner(self) -> Address: return Address(self._contract.functions.owner().call()) def set_owner(self, owner: Address) -> Transact: assert isinstance(owner, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, "setOwner", [owner.address]) def set_authority(self, ds_authority: Address): assert isinstance(ds_authority, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, "setAuthority", [ds_authority.address])
class DSPause(Contract): """A client for the `DSPause` contract, which schedules function calls after a predefined delay. You can find the source code of the `DSPause` contract here: <https://github.com/dapphub/ds-pause>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSPause` contract. """ class Plan: def __init__(self, usr: Address, fax: bytes, eta: datetime): """Creates a plan to be executed later. Args: usr: Address of the caller fax: Identifies the calldata eta: Identifies the earliest time of execution """ assert isinstance(usr, Address) assert isinstance(fax, bytes) assert isinstance(eta, datetime.datetime) self.usr = usr self.fax = fax self.eta = eta.timestamp() abi = Contract._load_abi(__name__, 'abi/DSPause.abi') bin = Contract._load_bin(__name__, 'abi/DSPause.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @staticmethod def deploy(web3: Web3, delay: int, owner: Address, ds_auth: DSAuth): return DSPause(web3=web3, address=Contract._deploy(web3, DSPause.abi, DSPause.bin, [delay, owner.address, ds_auth.address.address])) # TODO: Awaiting updated ABI/BIN from geb-deploy # def plot(self, plan: Plan): # return self._transact(plan, "plot") def drop(self, plan: Plan): return self._transact(plan, "drop") def exec(self, plan: Plan) -> Transact: return self._transact(plan, "exec") def _transact(self, plan: Plan, function_name: str) -> Transact: assert isinstance(plan, DSPause.Plan) assert isinstance(function_name, str) return Transact(self, self.web3, self.abi, self.address, self._contract, function_name, [plan.usr.address, plan.fax, int(plan.eta)])
class ProxyRegistry(Contract): """A client for the `ProxyRegistry` contract. Ref. <https://github.com/reflexer-labs/geb-proxy-actions/blob/master/src/GebProxyRegistry.sol> """ abi = Contract._load_abi(__name__, 'abi/ProxyRegistry.abi') bin = Contract._load_bin(__name__, 'abi/ProxyRegistry.bin') def __init__(self, web3: Web3, address: Address): assert isinstance(web3, Web3) assert isinstance(address, Address) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def build(self, owner: Address) -> Transact: assert isinstance(owner, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, 'build(address)', [owner.address]) def proxies(self, owner: Address) -> Address: assert isinstance(owner, Address) return Address(self._contract.functions.proxies(owner.address).call()) def __repr__(self): return f"ProxyRegistry('{self.address}')"
class DSVault(Contract): """A client for the `DSVault` contract. You can find the source code of the `DSVault` contract here: <https://github.com/dapphub/ds-vault>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSVault` contract. """ abi = Contract._load_abi(__name__, 'abi/DSVault.abi') bin = Contract._load_bin(__name__, 'abi/DSVault.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @staticmethod def deploy(web3: Web3): """Deploy a new instance of the `DSVault` contract. Args: web3: An instance of `Web` from `web3.py`. Returns: A `DSVault` class instance. """ return DSVault(web3=web3, address=Contract._deploy(web3, DSVault.abi, DSVault.bin, [])) def authority(self) -> Address: """Return the current `authority` of a `DSAuth`-ed contract. Returns: The address of the current `authority`. """ return Address(self._contract.functions.authority().call()) def set_authority(self, address: Address) -> Transact: """Set the `authority` of a `DSAuth`-ed contract. Args: address: The address of the new `authority`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'setAuthority', [address.address]) def __repr__(self): return f"DSVault('{self.address}')"
class DSGuard(Contract): """A client for the `DSGuard` contract. You can find the source code of the `DSGuard` contract here: <https://github.com/dapphub/ds-guard>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSGuard` contract. """ abi = Contract._load_abi(__name__, 'abi/DSGuard.abi') bin = Contract._load_bin(__name__, 'abi/DSGuard.bin') ANY = int_to_bytes32(2**256 - 1) def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @staticmethod def deploy(web3: Web3): return DSGuard(web3=web3, address=Contract._deploy(web3, DSGuard.abi, DSGuard.bin, [])) def permit(self, src, dst, sig: bytes) -> Transact: """Grant access to a function call. Args: src: Address of the caller, or `ANY`. dst: Address of the called contract, or `ANY`. sig: Signature of the called function, or `ANY`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(src, Address) or isinstance(src, bytes)) assert (isinstance(dst, Address) or isinstance(dst, bytes)) assert (isinstance(sig, bytes) and len(sig) in (4, 32)) if isinstance(src, Address) and isinstance(dst, Address): method = 'permit(address,address,bytes32)' src = src.address dst = dst.address else: method = 'permit(bytes32,bytes32,bytes32)' return Transact(self, self.web3, self.abi, self.address, self._contract, method, [src, dst, sig]) def __repr__(self): return f"DSGuard('{self.address}')"
class DSProxyCache(Contract): """A client for the `DSProxyCache` contract. Ref. <https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L120> """ abi = Contract._load_abi(__name__, 'abi/DSProxyCache.abi') bin = Contract._load_bin(__name__, 'abi/DSProxyCache.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @classmethod def deploy(cls, web3: Web3): return cls(web3=web3, address=Contract._deploy(web3, cls.abi, cls.bin, [])) def read(self, code: str) -> Optional[Address]: assert (isinstance(code, str)) if code.startswith('0x'): b32_code = hexstring_to_bytes(code) else: b32_code = hexstring_to_bytes('0x' + code) address = Address(self._contract.functions.read(b32_code).call()) if address == Address('0x0000000000000000000000000000000000000000'): return None else: return address def write(self, code: str): assert (isinstance(code, str)) if code.startswith('0x'): b32_code = hexstring_to_bytes(code) else: b32_code = hexstring_to_bytes('0x' + code) return Transact(self, self.web3, self.abi, self.address, self._contract, 'write', [b32_code]) def __repr__(self): return f"DSProxyCache('{self.address}')"
class GebProxyActions(Contract): """A client for the `GebProxyActionsDsr` contract. Ref. <https://github.com/reflexer-labs/geb-proxy-actions/blob/master/src/GebProxyActions.sol> """ abi = Contract._load_abi(__name__, 'abi/GebProxyActions.abi') bin = Contract._load_bin(__name__, 'abi/GebProxyActions.bin') def __init__(self, web3: Web3, address: Address): assert isinstance(web3, Web3) assert isinstance(address, Address) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address)
class DSRoles(Contract): """A client for the `DSRoles` contract, which manages lists of user roles and capabilities. You can find the source code of the `DSRoles` contract here: <https://github.com/dapphub/ds-roles>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSRoles` contract. """ abi = Contract._load_abi(__name__, 'abi/DSRoles.abi') bin = Contract._load_bin(__name__, 'abi/DSRoles.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def is_root_user(self, who: Address) -> bool: assert isinstance(who, Address) return bool(self._contract.functions.isUserRoot(who.address).call()) def set_root_user(self, who: Address, enabled=True) -> Transact: assert isinstance(who, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, "setRootUser", [who.address, enabled]) def has_user_role(self, who: Address, role: int) -> bool: assert isinstance(who, Address) assert isinstance(role, int) assert 0 <= role <= int('0xFFFFFFFF') return bool(self._contract.functions.hasUserRole(who.address, role).call()) def set_user_role(self, who: Address, role: int, enabled=True) -> Transact: assert isinstance(who, Address) assert isinstance(role, int) assert 0 <= role <= int('0xFFFFFFFF') return Transact(self, self.web3, self.abi, self.address, self._contract, "setUserRole", [who.address, role, enabled])
class ESM(Contract): """A client for the `ESM` contract, which allows users to call `global_settlement.shutdown_system()` and thereby trigger a shutdown. Ref. <https://github.com/reflexer-labs/esm/blob/master/src/ESM.sol> Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `ESM` contract.""" abi = Contract._load_abi(__name__, 'abi/ESM.abi') bin = Contract._load_bin(__name__, 'abi/ESM.bin') def __init__(self, web3: Web3, address: Address): assert isinstance(web3, Web3) assert isinstance(address, Address) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def authorized_accounts(self, address: Address) -> bool: """True if address is authorized""" return bool( self._contract.functions.authorizedAccounts( address.address).call()) def token_burner(self) -> Address: """ Return tokenBurner """ return Address(self._contract.functions.tokenBurner().call()) def trigger_threshold(self) -> Wad: """Minimum amount of Gov required to call `shutdown`""" return Wad(self._contract.functions.triggerThreshold().call()) def settled(self) -> bool: """True if `settle` has been called""" return bool(self._contract.functions.settled().call()) def shutdown(self): """Calls `shutdownSystem` on the `GlobalSettlement` contract, initiating a shutdown.""" logger.info("Calling shutdown to shutdown the global settlement") return Transact(self, self.web3, self.abi, self.address, self._contract, 'shutdown', [])
def deploy(web3: Web3): """Deploy a new instance of the `DSVault` contract. Args: web3: An instance of `Web` from `web3.py`. Returns: A `DSVault` class instance. """ return DSVault(web3=web3, address=Contract._deploy(web3, DSVault.abi, DSVault.bin, []))
def deploy(web3: Web3, zrx_asset: str): """Deploy a new instance of the 0x `Exchange` contract. Args: web3: An instance of `Web` from `web3.py`. zrx_token: The address of the ZRX token this exchange will use. Returns: A `ZrxExchange` class instance. """ return ZrxExchangeV2(web3=web3, address=Contract._deploy(web3, ZrxExchangeV2.abi, ZrxExchangeV2.bin, []))
def deploy(web3: Web3, name: str, symbol: str): """Deploy a new instance of the `DSToken` contract. Args: web3: An instance of `Web` from `web3.py`. name: Name of the new token. symbol: Symbol of the new token. Returns: A `DSToken` class instance. """ assert (isinstance(name, str)) assert (isinstance(symbol, str)) return DSToken(web3=web3, address=Contract._deploy( web3, DSToken.abi, DSToken.bin, [bytes(name, "utf-8"), bytes(symbol, "utf-8")]))
def approval_function(token: ERC20Token, spender_address: Address, spender_name: str): address_to_check = kwargs[ 'from_address'] if 'from_address' in kwargs else Address( token.web3.eth.defaultAccount) move_contract = Contract._get_contract(web3=token.web3, abi=move_abi, address=token.address) if move_contract.functions.safeRights( address_to_check.address, spender_address.address).call() is False: logger = logging.getLogger() logger.info( f"Approving {spender_name} ({spender_address}) to move our {token.address} directly" ) approve_safe_modification = Transact( move_contract, move_contract.web3, move_contract.abi, Address(move_contract.address), move_contract, 'approveSAFEModification', [spender_address.address]) if not approve_safe_modification.transact(**kwargs): raise RuntimeError("Approval failed!")
def deploy(cls, web3: Web3): return cls(web3=web3, address=Contract._deploy(web3, cls.abi, cls.bin, []))
class GlobalSettlement(Contract): """A client for the `GlobalSettlement` contract, used to orchestrate a shutdown. Ref. <https://github.com/reflexer-labs/geb/blob/master/src/GlobalSettlement.sol> Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `ESM` contract.""" abi = Contract._load_abi(__name__, 'abi/GlobalSettlement.abi') bin = Contract._load_bin(__name__, 'abi/GlobalSettlement.bin') def __init__(self, web3: Web3, address: Address): assert isinstance(web3, Web3) assert isinstance(address, Address) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def contract_enabled(self) -> bool: """True when enabled, false when disabled""" return self._contract.functions.contractEnabled().call() > 0 def authorized_accounts(self, address: Address) -> bool: """True if address is authorized""" return bool( self._contract.functions.authorizedAccounts( address.address).call()) def stability_fee_treasury(self) -> Address: """Return stabilityFeeTreasury""" return Address(self._contract.functions.stabilityFeeTreasury().call()) def shutdown_time(self) -> datetime: """Time of disable_contract""" timestamp = self._contract.functions.shutdownTime().call() return datetime.utcfromtimestamp(timestamp) def shutdown_cooldown(self) -> int: """Processing cooldown length, in seconds""" return int(self._contract.functions.shutdownCooldown().call()) def outstanding_coin_supply(self) -> Rad: """total outstanding system coin following processing""" return Rad(self._contract.functions.outstandingCoinSupply().call()) def final_coin_per_collateral_price( self, collateral_type: CollateralType) -> Ray: """Shutdown price for the collateral""" assert isinstance(collateral_type, CollateralType) return Ray( self._contract.functions.finalCoinPerCollateralPrice( collateral_type.toBytes()).call()) def collateral_shortfall(self, collateral_type: CollateralType) -> Wad: """Collateral shortfall (difference of debt and collateral""" assert isinstance(collateral_type, CollateralType) return Wad( self._contract.functions.collateralShortfall( collateral_type.toBytes()).call()) def collateral_total_debt(self, collateral_type: CollateralType) -> Wad: """Total debt for the collateral""" assert isinstance(collateral_type, CollateralType) return Wad( self._contract.functions.collateralTotalDebt( collateral_type.toBytes()).call()) def collateral_cash_price(self, collateral_type: CollateralType) -> Ray: """Final cash price for the collateral""" assert isinstance(collateral_type, CollateralType) return Ray( self._contract.functions.collateralCashPrice( collateral_type.toBytes()).call()) def coin_bag(self, address: Address) -> Wad: """Amount of system `prepare_coins_for_redeeming`ed for retrieving collateral in return""" assert isinstance(address, Address) return Wad(self._contract.functions.coinBag(address.address).call()) def coins_used_to_redeem(self, collateral_type: CollateralType, address: Address) -> Wad: assert isinstance(collateral_type, CollateralType) assert isinstance(address, Address) return Wad( self._contract.functions.coinsUsedToRedeem( collateral_type.toBytes(), address.address).call()) def freeze_collateral_type(self, collateral_type: CollateralType) -> Transact: """Set the `shutdownSystem` price for the collateral""" assert isinstance(collateral_type, CollateralType) return Transact(self, self.web3, self.abi, self.address, self._contract, 'freezeCollateralType(bytes32)', [collateral_type.toBytes()]) def fast_track_auction(self, collateral_type: CollateralType, collateral_auction_id: int) -> Transact: """Cancel a collateral auction and seize it's collateral""" assert isinstance(collateral_type, CollateralType) assert isinstance(collateral_auction_id, int) return Transact(self, self.web3, self.abi, self.address, self._contract, 'fastTrackAuction', [collateral_type.toBytes(), collateral_auction_id]) def process_safe(self, collateral_type: CollateralType, address: Address) -> Transact: """Cancels undercollateralized SAFE debt to determine collateral shortfall""" assert isinstance(collateral_type, CollateralType) assert isinstance(address, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, 'processSAFE', [collateral_type.toBytes(), address.address]) def free_collateral(self, collateral_type: CollateralType) -> Transact: """Releases excess collateral after `process_safe`ing""" assert isinstance(collateral_type, CollateralType) return Transact(self, self.web3, self.abi, self.address, self._contract, 'freeCollateral', [collateral_type.toBytes()]) def set_outstanding_coin_supply(self): """Fix the total outstanding supply of system coin""" return Transact(self, self.web3, self.abi, self.address, self._contract, 'setOutstandingCoinSupply', []) def calculate_cash_price(self, collateral_type: CollateralType) -> Transact: """Calculate the `fix`, the cash price for a given collateral""" assert isinstance(collateral_type, CollateralType) return Transact(self, self.web3, self.abi, self.address, self._contract, 'calculateCashPrice', [collateral_type.toBytes()]) def prepare_coins_for_redeeming(self, system_coin: Wad) -> Transact: """Deposit system coin into the `coin_bag`, from which it cannot be withdrawn""" assert isinstance(system_coin, Wad) return Transact(self, self.web3, self.abi, self.address, self._contract, 'prepareCoinsForRedeeming', [system_coin.value]) def redeem_collateral(self, collateral_type: CollateralType, system_coin: Wad): """Exchange an amount of system coin (already `prepare_coins_for_redeemin`ed in the `coin_bag`) for collateral""" assert isinstance(collateral_type, CollateralType) assert isinstance(system_coin, Wad) return Transact(self, self.web3, self.abi, self.address, self._contract, 'redeemCollateral', [collateral_type.toBytes(), system_coin.value])
class UniswapV2(Contract): """ UniswapV2 Python Client Each UniswapV2 instance is intended to be used with a single pool at a time. Documentation is available here: https://uniswap.org/docs/v2/ """ pair_abi = Contract._load_abi(__name__, 'abi/IUniswapV2Pair.abi') Irouter_abi = Contract._load_abi(__name__, 'abi/IUniswapV2Router02.abi')['abi'] router_abi = Contract._load_abi(__name__, 'abi/UniswapV2Router02.abi') router_bin = Contract._load_bin(__name__, 'abi/UniswapV2Router02.bin') Ifactory_abi = Contract._load_abi(__name__, 'abi/IUniswapV2Factory.abi')['abi'] factory_abi = Contract._load_abi(__name__, 'abi/UniswapV2Factory.abi') factory_bin = Contract._load_bin(__name__, 'abi/UniswapV2Factory.bin') def __init__(self, web3: Web3, token_a: Token, token_b: Token, keeper_address: Address, router_address: Address, factory_address: Address): assert (isinstance(web3, Web3)) assert (isinstance(token_a, Token)) assert (isinstance(token_b, Token)) assert (isinstance(keeper_address, Address)) assert (isinstance(router_address, Address)) assert (isinstance(factory_address, Address)) self.web3 = web3 self.token_a = token_a self.token_b = token_b self.router_address = router_address self.factory_address = factory_address self._router_contract = self._get_contract(web3, self.Irouter_abi, self.router_address) self._factory_contract = self._get_contract(web3, self.Ifactory_abi, self.factory_address) self.pair_address = self.get_pair_address(self.token_a.address, self.token_b.address) self.is_new_pool = self.pair_address == Address( "0x0000000000000000000000000000000000000000") if not self.is_new_pool: self.set_and_approve_pair_token(self.pair_address) self.account_address = keeper_address def set_and_approve_pair_token(self, pair_address: Address): self.pair_address = pair_address self._pair_contract = self._get_contract(self.web3, self.pair_abi['abi'], self.pair_address) self.pair_token = Token('Liquidity', self.pair_address, 18) self.is_new_pool = False def get_account_token_balance(self, token: Token) -> Wad: assert (isinstance(token, Token)) return token.normalize_amount( ERC20Token(web3=self.web3, address=token.address).balance_of(self.account_address)) def get_account_eth_balance(self) -> Wad: return Wad.from_number( Web3.fromWei( self.web3.eth.getBalance(self.account_address.address), 'ether')) def get_exchange_balance(self, token: Token, pair_address: Address) -> Wad: assert (isinstance(token, Token)) assert (isinstance(pair_address, Address)) return token.normalize_amount( ERC20Token(web3=self.web3, address=token.address).balance_of(pair_address)) def get_our_exchange_balance(self, token: Token, pair_address: Address) -> Wad: assert (isinstance(token, Token)) assert (isinstance(pair_address, Address)) if self.is_new_pool: return Wad.from_number(0) current_liquidity = self.get_current_liquidity() if current_liquidity == Wad.from_number(0): return Wad.from_number(0) total_liquidity = self.get_total_liquidity() exchange_balance = self.get_exchange_balance(token, pair_address) return current_liquidity * exchange_balance / total_liquidity # retrieve exchange rate for the instance's pair token def get_exchange_rate(self) -> Wad: token_a_reserve = self.get_exchange_balance(self.token_a, self.pair_address) token_b_reserve = self.get_exchange_balance(self.token_b, self.pair_address) if token_a_reserve == Wad.from_number( 0) or token_b_reserve == Wad.from_number(0): return Wad.from_number(0) else: return token_b_reserve / token_a_reserve # Return the total number of liquidity tokens minted for a given pair def get_total_liquidity(self) -> Wad: return Wad(self._pair_contract.functions.totalSupply().call()) # Return our liquidity token balance def get_current_liquidity(self) -> Wad: return Wad( self._pair_contract.functions.balanceOf( self.account_address.address).call()) # Return a pools minimum liquidity token balance def get_minimum_liquidity(self) -> Wad: return Wad( self._pair_contract.functions.MINIMUM_LIQUIDITY( self.account_address.address).call()) def get_pair_address(self, token_a_address: Address, token_b_address: Address) -> Address: assert (isinstance(token_a_address, Address)) assert (isinstance(token_b_address, Address)) return Address( self._factory_contract.functions.getPair( token_a_address.address, token_b_address.address).call()) def approve(self, token: Token): assert (isinstance(token, Token)) erc20_token = ERC20Token(self.web3, token.address) approval_function = directly() return approval_function(erc20_token, self.router_address, 'UniswapV2Router02') @staticmethod def _to_32byte_hex(val): return Web3.toHex(Web3.toBytes(val).rjust(32, b'\0')) def get_amounts_out(self, amount_in: Wad, tokens: List[Token]) -> List[Wad]: """ Calculate maximum output amount of a given input. Desired amount_in must be less than available liquidity or call will fail. Args: amounts_in: Desired amount of tokens out. tokens: List of tokens used to form a path for swap and normalize amounts for token decimals Returns: A list of uint256 reserve amounts required. """ assert (isinstance(amount_in, Wad)) assert (isinstance(tokens, List)) token_addresses = list(map(lambda token: token.address.address, tokens)) amounts = self._router_contract.functions.getAmountsOut( amount_in.value, token_addresses).call() wad_amounts = list( map(lambda amount: Wad.from_number(Web3.fromWei(amount, 'ether')), amounts)) for index, token in enumerate(tokens): wad_amounts[index] = token.normalize_amount(wad_amounts[index]) return wad_amounts def get_amounts_in(self, amount_out: Wad, path: List) -> List[Wad]: """ Calculate amount of given inputs to achieve an exact output amount. Desired amount_out must be less than available liquidity or call will fail. Args: amount_out: Desired amount of tokens out. path: List of addresses used to form a path for swap Returns: A list of uint256 reserve amounts required. """ assert (isinstance(amount_out, Wad)) assert (isinstance(path, List)) amounts = self._router_contract.functions.getAmountsIn( amount_out.value, path).call() return list( map(lambda amount: Wad.from_number(Web3.fromWei(amount, 'ether')), amounts)) def add_liquidity(self, amounts: dict, token_a: Token, token_b: Token) -> Transact: """ Add liquidity to arbitrary token pair. Args: amounts: dictionary[Wad, Wad, Wad, Wad] token_a: First token in the pool token_b: Second token in the pool Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amounts, dict)) assert (isinstance(token_a, Token)) assert (isinstance(token_b, Token)) addLiquidityArgs = [ token_a.address.address, token_b.address.address, amounts['amount_a_desired'].value, amounts['amount_b_desired'].value, amounts['amount_a_min'].value, amounts['amount_b_min'].value, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'addLiquidity', addLiquidityArgs) def add_liquidity_eth(self, amounts: dict, token: Token, eth_position: int) -> Transact: """ Add liquidity to token-weth pair. It is assumed that eth will always be token_a Args: amounts: dictionary[Wad, Wad, Wad, Wad] token_b: Token side of the pool Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amounts, dict)) assert (isinstance(token, Token)) assert (isinstance(eth_position, int)) if eth_position == 0: token_desired = amounts['amount_b_desired'].value token_min = amounts['amount_b_min'].value eth_desired = amounts['amount_a_desired'].value eth_min = amounts['amount_a_min'].value elif eth_position == 1: token_desired = amounts['amount_a_desired'].value token_min = amounts['amount_a_min'].value eth_desired = amounts['amount_b_desired'].value eth_min = amounts['amount_b_min'].value addLiquidityArgs = [ token.address.address, token_desired, token_min, eth_min, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'addLiquidityETH', addLiquidityArgs, {'value': eth_desired}) def remove_liquidity(self, amounts: dict, token_a: Token, token_b: Token) -> Transact: """ Remove liquidity from arbitrary token pair. Args: token_a: Address of pool token A. token_b: Address of pool token B. amounts: dictionary[uint256, uint256, uint256] Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(token_a, Token)) assert (isinstance(token_b, Token)) assert (isinstance(amounts, dict)) # Will approve Uniswap Liquidity token if allowance not already set self.approve(self.pair_token) removeLiquidityArgs = [ token_a.address.address, token_b.address.address, amounts['liquidity'].value, amounts['amountAMin'].value, amounts['amountBMin'].value, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'removeLiquidity', removeLiquidityArgs) def remove_liquidity_eth(self, amounts: dict, token: Token, eth_position: int) -> Transact: """ Remove liquidity from token-weth pair. Args: token_a: Address of pool token. amounts: dictionary[uint256, uint256, uint256] Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amounts, dict)) assert (isinstance(token, Token)) assert (isinstance(eth_position, int)) # Will approve Uniswap Liquidity token if allowance not already set self.approve(self.pair_token) if eth_position == 0: token_min = amounts['amountBMin'].value eth_min = amounts['amountAMin'].value elif eth_position == 1: token_min = amounts['amountAMin'].value eth_min = amounts['amountBMin'].value removeLiquidityArgs = [ token.address.address, amounts['liquidity'].value, token_min, eth_min, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'removeLiquidityETHSupportingFeeOnTransferTokens', removeLiquidityArgs) def swap_exact_eth_for_tokens(self, eth_to_swap: Wad, min_amount_out: Wad, path: List) -> Transact: """Convert ETH to Tokens. Requires Approval to have already been called on the token to swap Args: eth_to_swap: Amount of ETH to swap for token. min_amount_out: Minimum amount of output token to set price path: array of token addresses used to form swap route Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(eth_to_swap, Wad)) assert (isinstance(min_amount_out, Wad)) assert (isinstance(path, List)) swapArgs = [ min_amount_out.value, path, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'swapExactETHForTokens', swapArgs, {'value': eth_to_swap.value}) def swap_exact_tokens_for_tokens(self, tokens_to_swap: Wad, min_amount_out: Wad, path: List) -> Transact: """Convert ERC20 to ERC20. Requires Approval to have already been called on the token to swap Args: tokens_to_swap: Amount of given token to swap for token. min_amount_out: Minimum amount of output token to set price path: array of token addresses used to form swap route Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(tokens_to_swap, Wad)) assert (isinstance(min_amount_out, Wad)) assert (isinstance(path, List)) swapArgs = [ tokens_to_swap.value, min_amount_out.value, path, self.account_address.address, self._deadline() ] return Transact(self, self.web3, self.router_abi, self.router_address, self._router_contract, 'swapExactTokensForTokens', swapArgs) def _deadline(self) -> int: """Get a predefined deadline.""" return int(time.time()) + 1000 def __eq__(self, other): assert (isinstance(other, UniswapExchange)) return self.address == other.address def __repr__(self): return f"UniswapV2"
def deploy(web3: Web3): return TxManager(web3=web3, address=Contract._deploy(web3, TxManager.abi, TxManager.bin, []))
class TxManager(Contract): """A client for the `TxManager` contract. `TxManager` allows to invoke multiple contract methods in one Ethereum transaction. Each invocation is represented as an instance of the `Invocation` class, containing a contract address and a calldata. In addition to that, these invocations can use ERC20 token balances. In order to do that, the entire allowance of each token involved is transferred from the caller to the `TxManager` contract at the beginning of the transaction and all the remaining balances are returned to the caller at the end of it. In order to use this feature, ERC20 token allowances have to be granted to the `TxManager`. You can find the source code of the `TxManager` contract here: <https://github.com/reflexer-labs/geb-tx-manager>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `TxManager` contract. """ abi = Contract._load_abi(__name__, 'abi/TxManager.abi') bin = Contract._load_bin(__name__, 'abi/TxManager.bin') def __init__(self, web3: Web3, address: Address): assert(isinstance(web3, Web3)) assert(isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @staticmethod def deploy(web3: Web3): return TxManager(web3=web3, address=Contract._deploy(web3, TxManager.abi, TxManager.bin, [])) def approve(self, tokens: List[ERC20Token], approval_function): """Approve the TxManager contract to fully access balances of specified tokens. For available approval functions (i.e. approval modes) see `directly` and `via_tx_manager` in `pyflex.approval`. Args: tokens: List of :py:class:`pyflex.token.ERC20Token` class instances. approval_function: Approval function (i.e. approval mode). """ assert(isinstance(tokens, list)) assert(callable(approval_function)) for token in tokens: approval_function(token, self.address, 'TxManager') def owner(self) -> Address: return Address(self._contract.functions.owner().call()) def execute(self, tokens: List[Address], invocations: List[Invocation]) -> Transact: """Executes multiple contract methods in one Ethereum transaction. Args: tokens: List of addresses of ERC20 token the invocations should be able to access. invocations: A list of invocations (contract methods) to be executed. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ def token_addresses() -> list: return list(map(lambda address: address.address, tokens)) def script() -> bytes: return reduce(operator.add, map(lambda invocation: script_entry(invocation), invocations), bytes()) def script_entry(invocation: Invocation) -> bytes: address = invocation.address.as_bytes() calldata = invocation.calldata.as_bytes() calldata_length = len(calldata).to_bytes(32, byteorder='big') return address + calldata_length + calldata assert(isinstance(tokens, list)) assert(isinstance(invocations, list)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'execute', [token_addresses(), script()]) def __repr__(self): return f"TxManager('{self.address}')"
class TestUniswapV2(Contract): """ In order to run automated tests locally, all dependent contracts and deployable bytecode need to be available for deploying contract to local network. Deployable bytecode differs from the runtime bytecode you would see on Etherscan. """ pair_abi = Contract._load_abi(__name__, '../pyexchange/abi/IUniswapV2Pair.abi') Irouter_abi = Contract._load_abi( __name__, '../pyexchange/abi/IUniswapV2Router02.abi')['abi'] router_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV2Router02.abi') router_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV2Router02.bin') factory_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV2Factory.abi') factory_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV2Factory.bin') weth_abi = Contract._load_abi(__name__, '../pyexchange/abi/WETH.abi') weth_bin = Contract._load_bin(__name__, '../pyexchange/abi/WETH.bin') def setup_method(self): # Use Ganache docker container self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555")) self.web3.eth.defaultAccount = Web3.toChecksumAddress( "0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943") register_private_key( self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead" ) self.our_address = Address(self.web3.eth.defaultAccount) self.weth_address = self._deploy(self.web3, self.weth_abi, self.weth_bin, []) self.factory_address = self._deploy(self.web3, self.factory_abi, self.factory_bin, [self.our_address.address]) self.router_address = self._deploy( self.web3, self.router_abi, self.router_bin, [self.factory_address.address, self.weth_address.address]) self._weth_contract = self._get_contract(self.web3, self.weth_abi, self.weth_address) self.ds_systemcoin = DSToken.deploy(self.web3, 'SystemCoin', 'sys') self.ds_usdc = DSToken.deploy(self.web3, 'USDC', 'USDC') self.token_systemcoin = Token("SystemCoin", self.ds_systemcoin.address, 18) self.token_usdc = Token("USDC", self.ds_usdc.address, 6) self.token_weth = Token("WETH", self.weth_address, 18) self.systemcoin_usdc_uniswap = UniswapV2( self.web3, self.token_systemcoin, self.token_usdc, self.our_address, self.router_address, self.factory_address) self.systemcoin_eth_uniswap = UniswapV2( self.web3, self.token_systemcoin, self.token_weth, self.our_address, self.router_address, self.factory_address) ## Useful for debugging failing transactions logger = logging.getLogger('eth') logger.setLevel(8) # Transact.gas_estimate_for_bad_txs = 210000 def add_liquidity_tokens(self) -> Receipt: self.ds_systemcoin.mint(Wad( 17 * 10**18)).transact(from_address=self.our_address) self.ds_usdc.mint( self.token_usdc.unnormalize_amount( Wad.from_number(9))).transact(from_address=self.our_address) self.systemcoin_usdc_uniswap.approve(self.token_systemcoin) self.systemcoin_usdc_uniswap.approve(self.token_usdc) add_liquidity_tokens_args = { "amount_a_desired": Wad.from_number(1.9), "amount_b_desired": self.token_usdc.unnormalize_amount(Wad.from_number(2.0)), "amount_a_min": Wad.from_number(1.8), "amount_b_min": self.token_usdc.unnormalize_amount(Wad.from_number(1.9)) } return self.systemcoin_usdc_uniswap.add_liquidity( add_liquidity_tokens_args, self.token_systemcoin, self.token_usdc).transact(from_address=self.our_address) def add_liquidity_eth(self) -> Receipt: self.ds_systemcoin.mint(Wad( 300 * 10**18)).transact(from_address=self.our_address) self.systemcoin_eth_uniswap.approve(self.token_systemcoin) self.systemcoin_eth_uniswap.approve(self.token_weth) add_liquidity_eth_args = { "amount_b_desired": Wad.from_number(28), "amount_a_desired": Wad.from_number(.1), "amount_b_min": Wad.from_number(25), "amount_a_min": Wad.from_number(0.01) } return self.systemcoin_eth_uniswap.add_liquidity_eth( add_liquidity_eth_args, self.token_systemcoin, 0).transact(from_address=self.our_address) def test_approval(self): # given assert self.ds_systemcoin.allowance_of(self.our_address, self.router_address) == Wad(0) # when self.systemcoin_usdc_uniswap.approve(self.token_systemcoin) # then assert self.ds_systemcoin.allowance_of(self.our_address, self.router_address) > Wad(0) def test_getting_token_balances(self): # given self.ds_systemcoin.mint(Wad(17 * 10**18)).transact() self.ds_usdc.mint( self.token_usdc.unnormalize_amount(Wad.from_number(9))).transact() # when balance_systemcoin = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_systemcoin) balance_usdc = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_usdc) # then assert balance_systemcoin == Wad.from_number(17) assert balance_usdc == Wad.from_number(9) def test_add_liquidity_tokens(self): # when add_liquidity = self.add_liquidity_tokens() # then assert add_liquidity.successful == True # when self.systemcoin_usdc_uniswap.set_and_approve_pair_token( self.systemcoin_usdc_uniswap.get_pair_address( self.token_systemcoin.address, self.token_usdc.address)) # then assert self.systemcoin_usdc_uniswap.get_current_liquidity( ) > Wad.from_number(0) def test_add_liquidity_eth(self): # when add_liquidity_eth = self.add_liquidity_eth() # then assert add_liquidity_eth.successful == True # when self.systemcoin_eth_uniswap.set_and_approve_pair_token( self.systemcoin_usdc_uniswap.get_pair_address( self.token_systemcoin.address, self.token_weth.address)) # then assert self.systemcoin_eth_uniswap.get_current_liquidity( ) > Wad.from_number(0) def test_remove_liquidity_tokens(self): # given add_liquidity = self.add_liquidity_tokens() self.systemcoin_usdc_uniswap.set_and_approve_pair_token( self.systemcoin_usdc_uniswap.get_pair_address( self.token_systemcoin.address, self.token_usdc.address)) current_liquidity = self.systemcoin_usdc_uniswap.get_current_liquidity( ) total_liquidity = self.systemcoin_usdc_uniswap.get_total_liquidity() systemcoin_exchange_balance = self.systemcoin_usdc_uniswap.get_exchange_balance( self.token_systemcoin, self.systemcoin_usdc_uniswap.pair_address) usdc_exchange_balance = self.token_usdc.unnormalize_amount( self.systemcoin_usdc_uniswap.get_exchange_balance( self.token_usdc, self.systemcoin_usdc_uniswap.pair_address)) # then assert current_liquidity > Wad.from_number(0) assert total_liquidity > Wad.from_number(0) assert total_liquidity > current_liquidity # given amount_a_min = current_liquidity * systemcoin_exchange_balance / total_liquidity amount_b_min = current_liquidity * usdc_exchange_balance / total_liquidity remove_liquidity_tokens_args = { "liquidity": current_liquidity, "amountAMin": amount_a_min, "amountBMin": amount_b_min } # when remove_liquidity = self.systemcoin_usdc_uniswap.remove_liquidity( remove_liquidity_tokens_args, self.token_systemcoin, self.token_usdc).transact(from_address=self.our_address) # then assert remove_liquidity.successful == True assert self.systemcoin_usdc_uniswap.get_current_liquidity( ) == Wad.from_number(0) def test_remove_liquidity_eth(self): # given add_liquidity_eth = self.add_liquidity_eth() self.systemcoin_eth_uniswap.set_and_approve_pair_token( self.systemcoin_eth_uniswap.get_pair_address( self.token_systemcoin.address, self.token_weth.address)) current_liquidity = self.systemcoin_eth_uniswap.get_current_liquidity() total_liquidity = self.systemcoin_eth_uniswap.get_total_liquidity() systemcoin_exchange_balance = self.systemcoin_eth_uniswap.get_exchange_balance( self.token_systemcoin, self.systemcoin_eth_uniswap.pair_address) weth_exchange_balance = self.systemcoin_eth_uniswap.get_exchange_balance( self.token_weth, self.systemcoin_eth_uniswap.pair_address) # then assert current_liquidity > Wad.from_number(0) assert total_liquidity > Wad.from_number(0) assert total_liquidity > current_liquidity # given amount_a_min = current_liquidity * weth_exchange_balance / total_liquidity amount_b_min = current_liquidity * systemcoin_exchange_balance / total_liquidity remove_liquidity_eth_args = { "liquidity": current_liquidity, "amountBMin": amount_b_min, "amountAMin": amount_a_min } # when remove_liquidity = self.systemcoin_eth_uniswap.remove_liquidity_eth( remove_liquidity_eth_args, self.token_systemcoin, 0).transact(from_address=self.our_address) # then assert remove_liquidity.successful == True assert self.systemcoin_eth_uniswap.get_current_liquidity( ) == Wad.from_number(0) def test_tokens_swap(self): # given add_liquidity = self.add_liquidity_tokens() balance_systemcoin_before_swap = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_systemcoin) balance_usdc_before_swap = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_usdc) # when swap = self.systemcoin_usdc_uniswap.swap_exact_tokens_for_tokens( Wad.from_number(.2), self.token_usdc.unnormalize_amount(Wad.from_number(.01)), [self.ds_systemcoin.address.address, self.ds_usdc.address.address ]).transact(from_address=self.our_address) # then assert swap.successful == True balance_systemcoin_after_swap = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_systemcoin) balance_usdc_after_swap = self.systemcoin_usdc_uniswap.get_account_token_balance( self.token_usdc) assert balance_systemcoin_after_swap < balance_systemcoin_before_swap assert balance_usdc_before_swap < balance_usdc_after_swap
def deploy(web3: Web3): return DSValue(web3=web3, address=Contract._deploy(web3, DSValue.abi, DSValue.bin, []))
class SafeManager(Contract): """A client for the `GebSafeManger` contract, which is a wrapper around the safe system, for easier use. Ref. <https://github.com/reflexer-labs/geb-safe-manager/blob/master/src/GebSafeManager.sol> """ abi = Contract._load_abi(__name__, 'abi/GebSafeManager.abi') bin = Contract._load_bin(__name__, 'abi/GebSafeManager.bin') def __init__(self, web3: Web3, address: Address): assert isinstance(web3, Web3) assert isinstance(address, Address) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) self.safe_engine = SAFEEngine( self.web3, Address(self._contract.functions.safeEngine().call())) def open_safe(self, collateral_type: CollateralType, address: Address) -> Transact: assert isinstance(collateral_type, CollateralType) assert isinstance(address, Address) return Transact(self, self.web3, self.abi, self.address, self._contract, 'openSAFE', [collateral_type.toBytes(), address.address]) def safe(self, safeid: int) -> SAFE: '''Returns SAFE for respective SAFE ID''' assert isinstance(safeid, int) safe_address = Address(self._contract.functions.safes(safeid).call()) collateral_type = self.collateral_type(safeid) safe = self.safe_engine.safe(collateral_type, Address(safe_address)) return safe def owns_safe(self, safeid: int) -> Address: '''Returns owner Address of respective SAFE ID''' assert isinstance(safeid, int) owner = Address(self._contract.functions.ownsSAFE(safeid).call()) return owner def collateral_type(self, safeid: int) -> CollateralType: '''Returns CollateralType for respective SAFE ID''' assert isinstance(safeid, int) collateral_type = CollateralType.fromBytes( self._contract.functions.collateralTypes(safeid).call()) return collateral_type def first_safe_id(self, address: Address) -> int: '''Returns first SAFE Id created by owner address''' assert isinstance(address, Address) safeid = int( self._contract.functions.firstSAFEID(address.address).call()) return safeid def last_safe_id(self, address: Address) -> int: '''Returns last SAFE Id created by owner address''' assert isinstance(address, Address) safeid = self._contract.functions.lastSAFEID(address.address).call() return int(safeid) def safe_count(self, address: Address) -> int: '''Returns number of SAFE's created using the Geb-Safe-Manager contract specifically''' assert isinstance(address, Address) count = int(self._contract.functions.safeCount(address.address).call()) return count def __repr__(self): return f"SafeManager('{self.address}')"
def deploy(web3: Web3, delay: int, owner: Address, ds_auth: DSAuth): return DSPause(web3=web3, address=Contract._deploy(web3, DSPause.abi, DSPause.bin, [delay, owner.address, ds_auth.address.address]))
class DSToken(ERC20Token): """A client for the `DSToken` contract. You can find the source code of the `DSToken` contract here: <https://github.com/dapphub/ds-token>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSToken` contract. """ abi = Contract._load_abi(__name__, 'abi/DSToken.abi') bin = Contract._load_bin(__name__, 'abi/DSToken.bin') @staticmethod def deploy(web3: Web3, name: str, symbol: str): """Deploy a new instance of the `DSToken` contract. Args: web3: An instance of `Web` from `web3.py`. name: Name of the new token. symbol: Symbol of the new token. Returns: A `DSToken` class instance. """ assert (isinstance(name, str)) assert (isinstance(symbol, str)) return DSToken(web3=web3, address=Contract._deploy( web3, DSToken.abi, DSToken.bin, [bytes(name, "utf-8"), bytes(symbol, "utf-8")])) def owner(self) -> Address: """Return the current `authority` of a `DSAuth`-ed contract. Returns: The address of the current `authority`. """ return Address(self._contract.functions.owner().call()) def authority(self) -> Address: """Return the current `authority` of a `DSAuth`-ed contract. Returns: The address of the current `authority`. """ return Address(self._contract.functions.authority().call()) def set_authority(self, address: Address) -> Transact: """Set the `authority` of a `DSAuth`-ed contract. Args: address: The address of the new `authority`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'setAuthority', [address.address]) def mint(self, amount: Wad) -> Transact: """Increase the total supply of the token. Args: amount: The amount to increase the total supply by. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'mint(uint256)', [amount.value]) def mint_to(self, address: Address, amount: Wad) -> Transact: """Increase the total supply of the token. Args: address: The address to credit the new tokens to. amount: The amount to increase the total supply by. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'mint(address,uint256)', [address.address, amount.value]) def burn(self, amount: Wad) -> Transact: """Decrease the total supply of the token. Args: amount: The amount to decrease the total supply by. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'burn(uint256)', [amount.value]) def burn_from(self, address: Address, amount: Wad) -> Transact: """Decrease the total supply of the token. Args: address: The address to burn the tokens from. amount: The amount to decrease the total supply by. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'burn(address,uint256)', [address.address, amount.value]) def __repr__(self): return f"DSToken('{self.address}')"
class ERC20Token(Contract): """A client for a standard ERC20 token contract. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the ERC20 token. """ abi = Contract._load_abi(__name__, 'abi/ERC20Token.abi') registry = {} def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def name(self) -> str: abi_with_string = json.loads( """[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]""" ) abi_with_bytes32 = json.loads( """[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}]""" ) contract_with_string = self._get_contract(self.web3, abi_with_string, self.address) contract_with_bytes32 = self._get_contract(self.web3, abi_with_bytes32, self.address) try: return contract_with_string.functions.name().call() except: return str(contract_with_bytes32.functions.name().call(), "utf-8").strip('\x00') def symbol(self) -> str: abi_with_string = json.loads( """[{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]""" ) abi_with_bytes32 = json.loads( """[{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}]""" ) contract_with_string = self._get_contract(self.web3, abi_with_string, self.address) contract_with_bytes32 = self._get_contract(self.web3, abi_with_bytes32, self.address) try: return contract_with_string.functions.symbol().call() except: return str(contract_with_bytes32.functions.symbol().call(), "utf-8").strip('\x00') def total_supply(self) -> Wad: """Returns the total supply of the token. Returns: The total supply of the token. """ return Wad(self._contract.functions.totalSupply().call()) def balance_of(self, address: Address) -> Wad: """Returns the token balance of a given address. Args: address: The address to check the balance of. Returns: The token balance of the address specified. """ assert (isinstance(address, Address)) return Wad(self._contract.functions.balanceOf(address.address).call()) def balance_at_block(self, address: Address, block_identifier: int = 'latest') -> Wad: """Returns the token balance of a given address. Args: address: The address to check the balance of. block_identifier: block at which to retrieve the balance Returns: The token balance of the address specified. """ assert (isinstance(address, Address)) assert (isinstance(block_identifier, int) or block_identifier == 'latest') return Wad( self._contract.functions.balanceOf( address.address).call(block_identifier=block_identifier)) def allowance_of(self, address: Address, payee: Address) -> Wad: """Returns the current allowance of a specified `payee` (delegate account). Allowance is an ERC20 concept allowing the `payee` (delegate account) to spend a fixed amount of tokens on behalf of the token owner (`address`). Args: address: The address to check the allowance for (it's the address the tokens can be spent from). payee: The address of the delegate account (it's the address that can spend the tokens). Returns: The allowance of the `payee` specified in regards to the `address`. """ assert (isinstance(address, Address)) assert (isinstance(payee, Address)) return Wad( self._contract.functions.allowance(address.address, payee.address).call()) def transfer(self, address: Address, value: Wad) -> Transact: """Transfers tokens to a specified address. Args: address: Destination address to transfer the tokens to. value: The value of tokens to transfer. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(address, Address)) assert (isinstance(value, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'transfer', [address.address, value.value]) def transfer_from(self, source_address: Address, destination_address: Address, value: Wad) -> Transact: """Transfers tokens to a specified address. Args: source_address: Source address to transfer the tokens from. destination_address: Destination address to transfer the tokens to. value: The value of tokens to transfer. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(source_address, Address)) assert (isinstance(destination_address, Address)) assert (isinstance(value, Wad)) return Transact( self, self.web3, self.abi, self.address, self._contract, 'transferFrom', [source_address.address, destination_address.address, value.value]) def approve(self, payee: Address, limit: Wad = Wad(2**256 - 1)) -> Transact: """Modifies the current allowance of a specified `payee` (delegate account). Allowance is an ERC20 concept allowing the `payee` (delegate account) to spend a fixed amount of tokens (`limit`) on behalf of the token owner. If `limit` is omitted, a maximum possible value is granted. Args: payee: The address of the delegate account (it's the address that can spend the tokens). limit: The value of the allowance i.e. the value of tokens that the `payee` (delegate account) can spend on behalf of their owner. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(payee, Address)) assert (isinstance(limit, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'approve(address,uint256)', [payee.address, limit.value]) def __eq__(self, other): return self.address == other.address def __repr__(self): return f"ERC20Token('{self.address}')"
class DSProxyFactory(Contract): """A client for the `DSProxyFactory` contract. Ref. <https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L90> """ abi = Contract._load_abi(__name__, 'abi/DSProxyFactory.abi') bin = Contract._load_bin(__name__, 'abi/DSProxyFactory.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) @classmethod def deploy(cls, web3: Web3): return cls(web3=web3, address=Contract._deploy(web3, cls.abi, cls.bin, [])) def build(self) -> Transact: return Transact(self, self.web3, self.abi, self.address, self._contract, 'build()', []) def build_for(self, address: Address) -> Transact: assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'build(address)', [address.address]) def cache(self) -> Address: return Address(self._contract.functions.cache().call()) def is_proxy(self, address: Address) -> bool: assert (isinstance(address, Address)) return self._contract.functions.isProxy(address.address).call() def past_build(self, number_of_past_blocks: int, event_filter: dict = None) -> List[LogCreated]: """Synchronously retrieve past LogCreated events. `LogCreated` events are emitted every time someone build a proxy from the factory. Args: number_of_past_blocks: Number of past Ethereum blocks to retrieve the events from. event_filter: Filter which will be applied to returned events. Returns: List of past `LogCreated` events represented as :py:class:`pyflex.proxy.LogCreated` class. """ assert isinstance(number_of_past_blocks, int) assert isinstance(event_filter, dict) or (event_filter is None) return self._past_events(self._contract, 'Created', LogCreated, number_of_past_blocks, event_filter) @classmethod def log_created(cls, receipt: Receipt) -> List[LogCreated]: assert isinstance(receipt, Receipt) events = [] for log in receipt.raw_receipt.logs: try: event = LogCreated.from_event(dict(log)) events.append(event) except: pass return events def __repr__(self): return f"DSProxyFactory('{self.address}')"
def deploy(cls, web3: Web3, cache: Address): return cls(web3=web3, address=Contract._deploy(web3, cls.abi, cls.bin, [cache.address]))
class ZrxExchangeV2(Contract): """A client for the 0x V2 exchange contract. You can find the `0x V2` exchange contract here: <https://etherscan.io/address/0x4f833a24e1f95d70f028921e27040ca56e09ab0b>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the _0x_ `Exchange` contract. """ abi = Contract._load_abi(__name__, 'abi/ExchangeV2.abi') bin = Contract._load_bin(__name__, 'abi/ExchangeV2.bin') _ZERO_ADDRESS = Address("0x0000000000000000000000000000000000000000") ORDER_INFO_TYPE = '(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)' @staticmethod def deploy(web3: Web3, zrx_asset: str): """Deploy a new instance of the 0x `Exchange` contract. Args: web3: An instance of `Web` from `web3.py`. zrx_token: The address of the ZRX token this exchange will use. Returns: A `ZrxExchange` class instance. """ return ZrxExchangeV2(web3=web3, address=Contract._deploy(web3, ZrxExchangeV2.abi, ZrxExchangeV2.bin, [])) def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def zrx_asset(self) -> str: """Get the asset data of the ZRX token contract associated with this `ExchangeV2` contract. Returns: The asset data of the `ZRX` token. """ return str( bytes_to_hexstring( self._contract.functions.ZRX_ASSET_DATA().call())) def zrx_token(self) -> Address: """Get the address of the ZRX token contract associated with this `ExchangeV2` contract. Returns: The address of the `ZRX` token. """ return Address("0x" + self.zrx_asset()[-40:]) def asset_transfer_proxy(self, proxy_id: str) -> Address: """Get the address of the `ERC20Proxy` contract associated with this `Exchange` contract. Returns: The address of the `ERC20Proxy` token. """ assert (isinstance(proxy_id, str)) return Address( self._contract.functions.getAssetProxy( hexstring_to_bytes(proxy_id)).call()) def approve(self, tokens: List[ERC20Token], approval_function): """Approve the 0x ERC20Proxy contract to fully access balances of specified tokens. In case of 0x V2, it's the ERC20Proxy contract that actually gets the approvals, not the 0x Exchange contract itself. In addition to the tokens specified as the `tokens` parameter, the ZRX token always gets approved as well as without it the 0x Exchange contract wouldn't be able to charge maker and taker fees. For available approval functions (i.e. approval modes) see `directly` and `via_tx_manager` in `pyflex.approval`. Args: tokens: List of :py:class:`pyflex.token.ERC20Token` class instances. approval_function: Approval function (i.e. approval mode). """ assert (isinstance(tokens, list)) assert (callable(approval_function)) for token in tokens: # TODO + [ERC20Token(web3=self.web3, address=self.zrx_token())] approval_function(token, self.asset_transfer_proxy(ERC20Asset.ID), '0x ERC20Proxy contract') def past_fill(self, number_of_past_blocks: int, event_filter: dict = None) -> List[LogFill]: """Synchronously retrieve past LogFill events. `LogFill` events are emitted by the 0x contract every time someone fills an order. Args: number_of_past_blocks: Number of past Ethereum blocks to retrieve the events from. event_filter: Filter which will be applied to returned events. Returns: List of past `LogFill` events represented as :py:class:`pyflex.zrx.LogFill` class. """ assert (isinstance(number_of_past_blocks, int)) assert (isinstance(event_filter, dict) or (event_filter is None)) return self._past_events(self._contract, 'Fill', LogFill, number_of_past_blocks, event_filter) def past_cancel(self, number_of_past_blocks: int, event_filter: dict = None) -> List[LogCancel]: """Synchronously retrieve past LogCancel events. `LogCancel` events are emitted by the 0x contract every time someone cancels an order. Args: number_of_past_blocks: Number of past Ethereum blocks to retrieve the events from. event_filter: Filter which will be applied to returned events. Returns: List of past `LogCancel` events represented as :py:class:`pyflex.zrx.LogCancel` class. """ assert (isinstance(number_of_past_blocks, int)) assert (isinstance(event_filter, dict) or (event_filter is None)) return self._past_events(self._contract, 'Cancel', LogCancel, number_of_past_blocks, event_filter) def create_order(self, pay_asset: Asset, pay_amount: Wad, buy_asset: Asset, buy_amount: Wad, expiration: int) -> Order: """Creates a new order. The `maker_fee`, `taker_fee` and `fee_recipient` fields are by default set to zero. Before signing the order and submitting it to the relayer, they may need to be populated using the `calculate_fees()` method of the `ZrxRelayerApi` class. Args: pay_asset: The asset you want to put on sale. pay_amount: Amount of the `pay_asset` token you want to put on sale. buy_asset: The asset you want to be paid with. buy_amount: Amount of the `buy_asset` you want to receive. expiration: Unix timestamp (in seconds) when the order will expire. Returns: New order as an instance of the :py:class:`pyflex.zrx.Order` class. """ assert (isinstance(pay_asset, Asset)) assert (isinstance(pay_amount, Wad)) assert (isinstance(buy_asset, Asset)) assert (isinstance(buy_amount, Wad)) assert (isinstance(expiration, int)) return Order(exchange=self, sender=self._ZERO_ADDRESS, maker=Address(self.web3.eth.defaultAccount), taker=self._ZERO_ADDRESS, maker_fee=Wad(0), taker_fee=Wad(0), pay_asset=pay_asset, pay_amount=pay_amount, buy_asset=buy_asset, buy_amount=buy_amount, salt=self.generate_salt(), fee_recipient=self._ZERO_ADDRESS, expiration=expiration, exchange_contract_address=self.address, signature=None) def _get_order_info(self, order): assert (isinstance(order, Order)) method_signature = self.web3.keccak( text=f"getOrderInfo({self.ORDER_INFO_TYPE})")[0:4] method_parameters = encode_single(f"({self.ORDER_INFO_TYPE})", [self._order_tuple(order)]) request = bytes_to_hexstring(method_signature + method_parameters) response = self.web3.eth.call({ 'to': self.address.address, 'data': request }) response_decoded = decode_single("((uint8,bytes32,uint256))", response) return response_decoded def get_order_hash(self, order: Order) -> str: """Calculates hash of an order. Args: order: Order you want to calculate the hash of. Returns: Order hash as a hex string starting with `0x`. """ assert (isinstance(order, Order)) # the hash depends on the exchange contract address as well assert (order.exchange_contract_address == self.address) return bytes_to_hexstring(self._get_order_info(order)[0][1]) def get_unavailable_buy_amount(self, order: Order) -> Wad: """Return the order amount which was either taken or cancelled. Args: order: Order you want to get the unavailable amount of. Returns: The unavailable amount of the order (i.e. the amount which was either taken or cancelled), expressed in terms of the `buy_token` token. """ assert (isinstance(order, Order)) order_info = self._get_order_info(order)[0] if order_info[0] in [ 0, # INVALID, // Default value 1, # INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount 2, # INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount 4, # EXPIRED, // Order has already expired 5, # FULLY_FILLED, // Order is fully filled 6 ]: # CANCELLED // Order has been cancelled return order.buy_amount else: return Wad(order_info[2]) def sign_order(self, order: Order) -> Order: """Signs an order so it can be submitted to the relayer. Order will be signed by the `web3.eth.defaultAccount` account. Args: order: Order you want to sign. Returns: Signed order. Copy of the order passed as a parameter with the `signature` field filled with signature. """ assert (isinstance(order, Order)) signature = eth_sign(hexstring_to_bytes(self.get_order_hash(order)), self.web3) v, r, s = to_vrs(signature) signed_order = copy.copy(order) signed_order.signature = bytes_to_hexstring(bytes([v])) + \ bytes_to_hexstring(r)[2:] + \ bytes_to_hexstring(s)[2:] + \ "03" # EthSign return signed_order def fill_order(self, order: Order, fill_buy_amount: Wad) -> Transact: """Fills an order. Args: order: The order to be filled. fill_buy_amount: The amount (in terms of `buy_token` of the original order) to be filled. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(order, Order)) assert (isinstance(fill_buy_amount, Wad)) method_signature = self.web3.keccak( text=f"fillOrder({self.ORDER_INFO_TYPE},uint256,bytes)")[0:4] method_parameters = encode_single( f"({self.ORDER_INFO_TYPE},uint256,bytes)", [ self._order_tuple(order), fill_buy_amount.value, hexstring_to_bytes(order.signature) ]) request = bytes_to_hexstring(method_signature + method_parameters) return Transact(self, self.web3, self.abi, self.address, self._contract, None, [request]) def cancel_order(self, order: Order) -> Transact: """Cancels an order. Args: order: Order you want to cancel. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(order, Order)) method_signature = self.web3.keccak( text=f"cancelOrder({self.ORDER_INFO_TYPE})")[0:4] method_parameters = encode_single(f"({self.ORDER_INFO_TYPE})", [self._order_tuple(order)]) request = bytes_to_hexstring(method_signature + method_parameters) return Transact(self, self.web3, self.abi, self.address, self._contract, None, [request]) @staticmethod def _order_tuple(order): return (order.maker.address, order.taker.address, order.fee_recipient.address, order.sender.address, order.pay_amount.value, order.buy_amount.value, order.maker_fee.value, order.taker_fee.value, order.expiration, order.salt, hexstring_to_bytes(order.pay_asset.serialize()), hexstring_to_bytes(order.buy_asset.serialize())) @staticmethod def generate_salt() -> int: return int(time.time() * 1000) def __repr__(self): return f"ZrxExchangeV2('{self.address}')"
class DSProxy(Contract): """A client for the `DSProxy` contract. Ref. <https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L28> """ abi = Contract._load_abi(__name__, 'abi/DSProxy.abi') bin = Contract._load_bin(__name__, 'abi/DSProxy.bin') def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def authority(self) -> Address: """Return the current `authority` of a `DSAuth`-ed contract. Returns: The address of the current `authority`. """ return Address(self._contract.functions.authority().call()) def set_authority(self, address: Address) -> Transact: """Set the `authority` of a `DSAuth`-ed contract. Args: address: The address of the new `authority`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'setAuthority', [address.address]) @classmethod def deploy(cls, web3: Web3, cache: Address): return cls(web3=web3, address=Contract._deploy(web3, cls.abi, cls.bin, [cache.address])) def execute(self, code: str, calldata: Calldata) -> Transact: assert (isinstance(code, str)) assert (isinstance(calldata, Calldata)) if code.startswith('0x'): b32_code = hexstring_to_bytes(code) else: b32_code = hexstring_to_bytes('0x' + code) return Transact(self, self.web3, self.abi, self.address, self._contract, 'execute(bytes,bytes)', [b32_code, calldata.as_bytes()]) def call(self, code: str, calldata: Calldata) -> (Address, HexBytes): assert (isinstance(code, str)) assert (isinstance(calldata, Calldata)) fn = self._contract.get_function_by_signature('execute(bytes,bytes)') target, response = fn(code, calldata.value).call() return Address(target), HexBytes(response) def execute_at(self, address: Address, calldata: Calldata) -> Transact: assert (isinstance(address, Address)) assert (isinstance(calldata, Calldata)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'execute(address,bytes)', [address.address, calldata.as_bytes()]) def call_at(self, address: Address, calldata: Calldata) -> Transact: assert (isinstance(address, Address)) assert (isinstance(calldata, Calldata)) fn = self._contract.get_function_by_signature('execute(address,bytes)') response = fn(address.address, calldata.value).call() return HexBytes(response) def set_cache(self, address: Address) -> Transact: assert (isinstance(address, Address)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'setCache', [address.address]) def cache(self) -> Address: return Address(self._contract.functions.cache().call()) def __repr__(self): return f"DSProxy('{self.address}')"
class DSValue(DSAuth): """A client for the `DSValue` contract, a single-value data feed. `DSValue` is a single-value data feed, which means it can be in one of two states. It can either contain a value (in which case `has_value()` returns `True` and the read methods return that value) or be empty (in which case `has_value()` returns `False` and the read methods throw exceptions). `DSValue` can be populated with a new value using `updateResult()` and cleared using `void()`. Everybody can read from a `DSValue`. Calling `updateResult()` and `void()` is usually whitelisted to some addresses only. upda The `DSValue` contract keeps the value as a 32-byte array (Ethereum `bytes32` type). Methods have been provided to cast it into `int`, read as hex etc. You can find the source code of the `DSValue` contract here: <https://github.com/dapphub/ds-value>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSValue` contract. """ abi = Contract._load_abi(__name__, 'abi/DSValue.abi') bin = Contract._load_bin(__name__, 'abi/DSValue.bin') @staticmethod def deploy(web3: Web3): return DSValue(web3=web3, address=Contract._deploy(web3, DSValue.abi, DSValue.bin, [])) def __init__(self, web3: Web3, address: Address): assert (isinstance(web3, Web3)) assert (isinstance(address, Address)) self.web3 = web3 self.address = address self._contract = self._get_contract(web3, self.abi, address) def has_value(self) -> bool: """Checks whether this instance contains a value. Returns: `True` if this instance contains a value, which can be read. `False` otherwise. """ return self._contract.functions.getResultWithValidity().call()[1] def read(self) -> int: """Reads the current value from this instance If this instance does not contain a value, throws an exception. Returns: An integer with the current value of this instance. """ return self._contract.functions.read().call() def update_result(self, new_value: int) -> Transact: """Populates this instance with a new value. Args: new_value: An integer of the new value to be set. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(new_value, int)) #assert(len(new_value) == 32) return Transact(self, self.web3, self.abi, self.address, self._contract, 'updateResult', [new_value]) def restart_value(self) -> Transact: """Removes the current value from this instance. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ return Transact(self, self.web3, self.abi, self.address, self._contract, 'restartValue', []) def __repr__(self): return f"DSValue('{self.address}')"
class DSEthToken(ERC20Token): """A client for the `DSEthToken` contract. `DSEthToken`, also known as ETH Wrapper or W-ETH, is a contract into which you can deposit raw ETH and then deal with it like with any other ERC20 token. In addition to the `deposit()` and `withdraw()` methods, it implements the standard ERC20 token API. You can find the source code of the `DSEthToken` contract here: <https://github.com/dapphub/ds-eth-token>. Attributes: web3: An instance of `Web` from `web3.py`. address: Ethereum address of the `DSEthToken` contract. """ abi = Contract._load_abi(__name__, 'abi/DSEthToken.abi') bin = Contract._load_bin(__name__, 'abi/DSEthToken.bin') @staticmethod def deploy(web3: Web3): """Deploy a new instance of the `DSEthToken` contract. Args: web3: An instance of `Web` from `web3.py`. Returns: A `DSEthToken` class instance. """ return DSEthToken(web3=web3, address=Contract._deploy(web3, DSEthToken.abi, DSEthToken.bin, [])) def __init__(self, web3, address): super().__init__(web3, address) self._contract = self._get_contract(web3, self.abi, address) def deposit(self, amount: Wad) -> Transact: """Deposits `amount` of raw ETH to `DSEthToken`. Args: amount: Amount of raw ETH to be deposited to `DSEthToken`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'deposit', [], {'value': amount.value}) def withdraw(self, amount: Wad) -> Transact: """Withdraws `amount` of raw ETH from `DSEthToken`. The withdrawn ETH will get transferred to the calling account. Args: amount: Amount of raw ETH to be withdrawn from `DSEthToken`. Returns: A :py:class:`pyflex.Transact` instance, which can be used to trigger the transaction. """ assert (isinstance(amount, Wad)) return Transact(self, self.web3, self.abi, self.address, self._contract, 'withdraw', [amount.value]) def __repr__(self): return f"DSEthToken('{self.address}')"