Exemple #1
0
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])
Exemple #2
0
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)])
Exemple #3
0
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}')"
Exemple #4
0
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}')"
Exemple #5
0
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}')"
Exemple #6
0
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}')"
Exemple #7
0
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)
Exemple #8
0
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])
Exemple #9
0
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', [])
Exemple #10
0
    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,
                                                []))
Exemple #11
0
    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, []))
Exemple #12
0
    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")]))
Exemple #13
0
    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!")
Exemple #14
0
 def deploy(cls, web3: Web3):
     return cls(web3=web3,
                address=Contract._deploy(web3, cls.abi, cls.bin, []))
Exemple #15
0
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])
Exemple #16
0
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"
Exemple #17
0
 def deploy(web3: Web3):
     return TxManager(web3=web3, address=Contract._deploy(web3, TxManager.abi, TxManager.bin, []))
Exemple #18
0
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
Exemple #20
0
 def deploy(web3: Web3):
     return DSValue(web3=web3,
                    address=Contract._deploy(web3, DSValue.abi, DSValue.bin,
                                             []))
Exemple #21
0
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}')"
Exemple #22
0
 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]))
Exemple #23
0
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}')"
Exemple #24
0
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}')"
Exemple #25
0
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}')"
Exemple #26
0
 def deploy(cls, web3: Web3, cache: Address):
     return cls(web3=web3,
                address=Contract._deploy(web3, cls.abi, cls.bin,
                                         [cache.address]))
Exemple #27
0
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}')"
Exemple #28
0
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}')"
Exemple #29
0
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}')"
Exemple #30
0
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}')"