Exemplo n.º 1
0
class DSEthToken(ERC20Token):
    """A client for a the `DSEthToken` contract.

    `DSEthToken`, also known as _ETH Wrapper_ or _W-ETH_, is a smart 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.

    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, args=[]):
        return DSEthToken(web3=web3,
                          address=Contract._deploy(web3, DSEthToken.abi,
                                                   DSEthToken.bin, args))

    def __init__(self, web3, address):
        super().__init__(web3, address)
        self._contract = web3.eth.contract(abi=self.abi)(
            address=address.address)

    def deposit(self, amount: Wad) -> Optional[Receipt]:
        """Deposits `amount` of raw ETH to `DSEthToken`.

        Args:
            amount: Amount of raw ETH to be deposited to `DSEthToken`.

        Returns:
            A `Receipt` if the Ethereum transaction was successful and the amount has been deposited.
            `None` if the Ethereum transaction failed.
        """
        assert (isinstance(amount, Wad))
        return self._transact(
            self.web3,
            f"DSEthToken('{self.address}').deposit() with value='{amount}'",
            lambda: self._contract.transact({
                'value': amount.value
            }).deposit())

    def withdraw(self, amount: Wad) -> Optional[Receipt]:
        """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 `Receipt` if the Ethereum transaction was successful and the amount has been withdrawn.
            `None` if the Ethereum transaction failed.
        """
        assert (isinstance(amount, Wad))
        return self._transact(
            self.web3, f"DSEthToken('{self.address}').withdraw('{amount}')",
            lambda: self._contract.transact().withdraw(amount.value))
Exemplo n.º 2
0
class DSToken(ERC20Token):
    abi = Contract._load_abi(__name__, 'abi/DSToken.abi')
    bin = Contract._load_bin(__name__, 'abi/DSToken.bin')

    @staticmethod
    def deploy(web3: Web3, symbol: str):
        assert (isinstance(symbol, str))
        return DSToken(web3=web3,
                       address=Contract._deploy(web3, DSToken.abi, DSToken.bin,
                                                [symbol]))

    def set_authority(self, address: Address) -> Optional[Receipt]:
        assert (isinstance(address, Address))
        return self._transact(
            self.web3, f"DSToken('{self.address}').setAuthority('{address}')",
            lambda: self._contract.transact().setAuthority(address.address))

    def mint(self, amount: Wad) -> Optional[Receipt]:
        assert (isinstance(amount, Wad))
        return self._transact(
            self.web3, f"DSToken('{self.address}').mint('{amount}')",
            lambda: self._contract.transact().mint(amount.value))

    def burn(self, amount: Wad) -> Optional[Receipt]:
        assert (isinstance(amount, Wad))
        return self._transact(
            self.web3, f"DSToken('{self.address}').burn('{amount}')",
            lambda: self._contract.transact().burn(amount.value))
Exemplo n.º 3
0
class DSRoles(Contract):
    abi = Contract._load_abi(__name__, 'abi/DSRoles.abi')
    bin = Contract._load_bin(__name__, 'abi/DSRoles.bin')

    def __init__(self, web3, address):
        self.web3 = web3
        self.address = address
        self._contract = web3.eth.contract(abi=self.abi)(address=address.address)

    @staticmethod
    def deploy(web3: Web3):
        return DSRoles(web3=web3, address=Contract._deploy(web3, DSRoles.abi, DSRoles.bin, []))
Exemplo n.º 4
0
class Top(Contract):
    """A client for the `Top` contract, one of the `SAI Stablecoin System` contracts.

    Attributes:
        web3: An instance of `Web` from `web3.py`.
        address: Ethereum address of the `Top` contract.
    """

    abi = Contract._load_abi(__name__, 'abi/Top.abi')
    bin = Contract._load_bin(__name__, 'abi/Top.bin')

    def __init__(self, web3: Web3, address: Address):
        self.web3 = web3
        self.address = address
        self._assert_contract_exists(web3, address)
        self._contract = web3.eth.contract(abi=self.abi)(address=address.address)

    @staticmethod
    def deploy(web3: Web3, tub: Address, tap: Address):
        assert(isinstance(tub, Address))
        assert(isinstance(tap, Address))
        return Top(web3=web3, address=Contract._deploy(web3, Top.abi, Top.bin, [tub.address, tap.address]))

    def set_authority(self, address: Address) -> Optional[Receipt]:
        assert(isinstance(address, Address))
        return self._transact(self.web3, f"Top('{self.address}').setAuthority('{address}')",
                              lambda: self._contract.transact().setAuthority(address.address))

    def fix(self) -> Ray:
        """Get the GEM per SAI settlement price.

        Returns:
            The GEM per SAI settlement (kill) price.
        """
        return Ray(self._contract.call().fix())

    # TODO cage
    # TODO cash
    # TODO vent

    def __eq__(self, other):
        assert(isinstance(other, Top))
        return self.address == other.address

    def __repr__(self):
        return f"Top('{self.address}')"
Exemplo n.º 5
0
class DSVault(Contract):
    abi = Contract._load_abi(__name__, 'abi/DSVault.abi')
    bin = Contract._load_bin(__name__, 'abi/DSVault.bin')

    def __init__(self, web3, address):
        self.web3 = web3
        self.address = address
        self._contract = web3.eth.contract(abi=self.abi)(
            address=address.address)

    @staticmethod
    def deploy(web3: Web3):
        return DSVault(web3=web3,
                       address=Contract._deploy(web3, DSVault.abi, DSVault.bin,
                                                []))

    def set_authority(self, address: Address) -> Optional[Receipt]:
        assert (isinstance(address, Address))
        return self._transact(
            self.web3, f"DSVault('{self.address}').setAuthority('{address}')",
            lambda: self._contract.transact().setAuthority(address.address))
Exemplo n.º 6
0
class 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, address):
        self.web3 = web3
        self.address = address
        self._contract = web3.eth.contract(abi=self.abi)(address=address.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) -> Optional[Receipt]:
        assert(isinstance(src, Address) or isinstance(src, bytes))
        assert(isinstance(dst, Address) or isinstance(dst, bytes))
        assert(isinstance(sig, bytes) and len(sig) == 32)

        if isinstance(src, Address):
            src = src.address
        if isinstance(dst, Address):
            dst = dst.address

        return self._transact(self.web3, f"DSGuard('{self.address}').permit('{src}', '{dst}', '{bytes_to_hexstring(sig)}')",
                              lambda: self._contract.transact().permit(src, dst, sig))

    def forbid(self, src: Address, dst: Address, sig: bytes) -> Optional[Receipt]:
        assert(isinstance(src, Address) or isinstance(src, bytes))
        assert(isinstance(dst, Address) or isinstance(dst, bytes))
        assert(isinstance(sig, bytes) and len(sig) == 32)

        if isinstance(src, Address):
            src = src.address
        if isinstance(dst, Address):
            dst = dst.address

        return self._transact(self.web3, f"DSGuard('{self.address}').forbid('{src}', '{dst}', '{bytes_to_hexstring(sig)}')",
                              lambda: self._contract.transact().forbid(src, dst, sig))
Exemplo n.º 7
0
class Tap(Contract):
    """A client for the `Tap` contract, on of the contracts driving the `SAI Stablecoin System`.

    Attributes:
        web3: An instance of `Web` from `web3.py`.
        address: Ethereum address of the `Tap` contract.
    """

    abi = Contract._load_abi(__name__, 'abi/Tap.abi')
    bin = Contract._load_bin(__name__, 'abi/Tap.bin')

    def __init__(self, web3: Web3, address: Address):
        self.web3 = web3
        self.address = address
        self._assert_contract_exists(web3, address)
        self._contract = web3.eth.contract(abi=self.abi)(address=address.address)

    @staticmethod
    def deploy(web3: Web3, tub: Address, pit: Address):
        assert(isinstance(tub, Address))
        assert(isinstance(pit, Address))
        return Tap(web3=web3, address=Contract._deploy(web3, Tap.abi, Tap.bin, [tub.address, pit.address]))

    def set_authority(self, address: Address) -> Optional[Receipt]:
        assert(isinstance(address, Address))
        return self._transact(self.web3, f"Tap('{self.address}').setAuthority('{address}')",
                              lambda: self._contract.transact().setAuthority(address.address))

    def woe(self) -> Wad:
        """Get the amount of bad debt.

        Returns:
            The amount of bad debt in SAI.
        """
        return Wad(self._contract.call().woe())

    def fog(self) -> Wad:
        """Get the amount of SKR pending liquidation.

        Returns:
            The amount of SKR pending liquidation, in SKR.
        """
        return Wad(self._contract.call().fog())

    #TODO beware that it doesn't call drip() underneath so if `tax`>1.0 we won't get an up-to-date value of joy()
    def joy(self) -> Wad:
        """Get the amount of surplus SAI.

        Surplus SAI can be processed using `boom()`.

        Returns:
            The amount of surplus SAI accumulated in the Tub.
        """
        return Wad(self._contract.call().joy())

    def gap(self) -> Wad:
        """Get the current spread for `boom` and `bust`.

        Returns:
            The current spread for `boom` and `bust`. `1.0` means no spread, `1.01` means 1% spread.
        """
        return Wad(self._contract.call().gap())

    def jump(self, new_gap: Wad) -> Optional[Receipt]:
        """Update the current spread (`gap`) for `boom` and `bust`.

        Args:
            new_tax: The new value of the spread (`gap`). `1.0` means no spread, `1.01` means 1% spread.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_gap, Wad)
        return self._transact(self.web3, f"Tap('{self.address}').jump('{new_gap}')",
                              lambda: self._contract.transact().jump(new_gap.value))

    def s2s(self) -> Wad:
        """Get the current SKR per SAI rate (for `boom` and `bust`).

        Returns:
            The current SKR per SAI rate.
        """
        return Wad(self._contract.call().s2s())

    def bid(self) -> Wad:
        """Get the current price of SKR in SAI for `boom`.

        Returns:
            The SKR in SAI price that will be used on `boom()`.
        """
        return Wad(self._contract.call().bid())

    def ask(self) -> Wad:
        """Get the current price of SKR in SAI for `bust`.

        Returns:
            The SKR in SAI price that will be used on `bust()`.
        """
        return Wad(self._contract.call().ask())

    def boom(self, amount_in_skr: Wad) -> Optional[Receipt]:
        """Buy some amount of SAI to process `joy` (surplus).

        Args:
            amount_in_skr: The amount of SKR we want to send in order to receive SAI.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(amount_in_skr, Wad)
        return self._transact(self.web3, f"Tap('{self.address}').boom('{amount_in_skr}')",
                              lambda: self._contract.transact().boom(amount_in_skr.value))

    def boom_calldata(self, amount_in_skr: Wad) -> Calldata:
        return Calldata(self.web3.eth.contract(abi=self.abi).encodeABI('boom', [amount_in_skr]))

    def bust(self, amount_in_skr: Wad) -> Optional[Receipt]:
        """Sell some amount of SAI to process `woe` (bad debt).

        Args:
            amount_in_skr: The amount of SKR we want to receive in exchange for our SAI.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(amount_in_skr, Wad)
        return self._transact(self.web3, f"Tap('{self.address}').bust('{amount_in_skr}')",
                              lambda: self._contract.transact().bust(amount_in_skr.value))

    def bust_calldata(self, amount_in_skr: Wad) -> Calldata:
        return Calldata(self.web3.eth.contract(abi=self.abi).encodeABI('bust', [amount_in_skr]))

    def __eq__(self, other):
        assert(isinstance(other, Tap))
        return self.address == other.address

    def __repr__(self):
        return f"Tap('{self.address}')"
Exemplo n.º 8
0
class Tub(Contract):
    """A client for the `Tub` contract, the primary contract driving the `SAI Stablecoin System`.

    SAI is a simple version of the diversely collateralized DAI stablecoin.

    In this model there is one type of underlying collateral (called gems).
    The SKR token represents claims on the system's excess gems, and is the
    only admissible type of collateral.  Gems can be converted to/from SKR.

    Any transfers of SAI or SKR are done using the normal ERC20 interface;
    until settlement mode is triggered, SAI users should only need ERC20.
    ``ERC20Token`` class may be used for it.

    Attributes:
        web3: An instance of `Web` from `web3.py`.
        address: Ethereum address of the `Tub` contract.
    """

    abiTub = Contract._load_abi(__name__, 'abi/Tub.abi')
    binTub = Contract._load_bin(__name__, 'abi/Tub.bin')
    abiTip = Contract._load_abi(__name__, 'abi/Tip.abi')
    abiJar = Contract._load_abi(__name__, 'abi/SaiJar.abi')
    abiJug = Contract._load_abi(__name__, 'abi/SaiJug.abi')

    def __init__(self, web3: Web3, address: Address):
        self.web3 = web3
        self.address = address
        self._assert_contract_exists(web3, address)
        self._contractTub = web3.eth.contract(abi=self.abiTub)(address=address.address)
        self._contractTip = web3.eth.contract(abi=self.abiTip)(address=self._contractTub.call().tip())
        self._contractJar = web3.eth.contract(abi=self.abiJar)(address=self._contractTub.call().jar())
        self._contractJug = web3.eth.contract(abi=self.abiJug)(address=self._contractTub.call().jug())

    @staticmethod
    def deploy(web3: Web3, jar: Address, jug: Address, pot: Address, pit: Address, tip: Address):
        assert(isinstance(jar, Address))
        assert(isinstance(jug, Address))
        assert(isinstance(pot, Address))
        assert(isinstance(pit, Address))
        assert(isinstance(tip, Address))
        return Tub(web3=web3, address=Contract._deploy(web3, Tub.abiTub, Tub.binTub,
                                                       [jar.address, jug.address, pot.address, pit.address, tip.address]))

    def set_authority(self, address: Address) -> Optional[Receipt]:
        assert(isinstance(address, Address))
        self._transact(self.web3, f"Tub('{self.address}').setAuthority('{address}')",
                       lambda: self._contractTub.transact().setAuthority(address.address))
        self._transact(self.web3, f"Tip('{self._contractTub.call().tip()}').setAuthority('{address}')",
                       lambda: self._contractTip.transact().setAuthority(address.address))
        self._transact(self.web3, f"SaiJar('{self._contractTub.call().jar()}').setAuthority('{address}')",
                       lambda: self._contractJar.transact().setAuthority(address.address))
        return self._transact(self.web3, f"SaiJug('{self._contractTub.call().jug()}').setAuthority('{address}')",
                       lambda: self._contractJug.transact().setAuthority(address.address))

    def approve(self, approval_function):
        approval_function(ERC20Token(web3=self.web3, address=self.gem()), self.jar(), 'Tub.jar')
        approval_function(ERC20Token(web3=self.web3, address=self.skr()), self.jar(), 'Tub.jar')
        approval_function(ERC20Token(web3=self.web3, address=self.sai()), self.pot(), 'Tub.pot')
        approval_function(ERC20Token(web3=self.web3, address=self.skr()), self.pit(), 'Tub.pit')
        approval_function(ERC20Token(web3=self.web3, address=self.sai()), self.pit(), 'Tub.pit')

    def sai(self) -> Address:
        """Get the SAI token.

        Returns:
            The address of the SAI token.
        """
        return Address(self._contractTub.call().sai())

    def sin(self) -> Address:
        """Get the SIN token.

        Returns:
            The address of the SIN token.
        """
        return Address(self._contractTub.call().sin())

    def jug(self) -> Address:
        """Get the SAI/SIN tracker.

        Returns:
            The address of the SAI/SIN tracker token.
        """
        return Address(self._contractTub.call().jug())

    def jar(self) -> Address:
        """Get the collateral vault.

        Returns:
            The address of the `SaiJar` vault. It is an internal component of Sai.
        """
        return Address(self._contractTub.call().jar())

    def pit(self) -> Address:
        """Get the liquidator vault.

        Returns:
            The address of the `DSVault` holding the bad debt.
        """
        return Address(self._contractTub.call().pit())

    def pot(self) -> Address:
        """Get the good debt vault.

        Returns:
            The address of the `DSVault` holding the good debt.
        """
        return Address(self._contractTub.call().pot())

    def skr(self) -> Address:
        """Get the SKR token.

        Returns:
            The address of the SKR token.
        """
        return Address(self._contractTub.call().skr())

    def gem(self) -> Address:
        """Get the collateral token (eg. W-ETH).

        Returns:
            The address of the collateral token.
        """
        return Address(self._contractTub.call().gem())

    def pip(self) -> Address:
        """Get the GEM price feed.

        Returns:
            The address of the GEM price feed, which could be a `DSValue`, a `DSCache`, a `Mednianizer` etc.
        """
        return Address(self._contractJar.call().pip())

    def tip(self) -> Address:
        """Get the target price engine.

        Returns:
            The address of the target price engine. It is an internal component of Sai.
        """
        return Address(self._contractTub.call().tip())

    def axe(self) -> Ray:
        """Get the liquidation penalty.

        Returns:
            The liquidation penalty. `1.0` means no penalty. `1.2` means 20% penalty.
        """
        return Ray(self._contractTub.call().axe())

    def hat(self) -> Wad:
        """Get the debt ceiling.

        Returns:
            The debt ceiling in SAI.
        """
        return Wad(self._contractTub.call().hat())

    def mat(self) -> Ray:
        """Get the liquidation ratio.

        Returns:
            The liquidation ratio. `1.5` means the liquidation ratio is 150%.
        """
        return Ray(self._contractTub.call().mat())

    def tax(self) -> Ray:
        """Get the stability fee.

        Returns:
            Per-second value of the stability fee. `1.0` means no stability fee.
        """
        return Ray(self._contractTub.call().tax())

    def way(self) -> Ray:
        """Get the holder fee (interest rate).

        Returns:
            Per-second value of the holder fee. `1.0` means no holder fee.
        """
        return Ray(self._contractTip.call().way())

    def reg(self) -> int:
        """Get the Tub stage ('register').

        Returns:
            The current Tub stage (0=Usual, 1=Caged).
        """
        return self._contractTub.call().reg()

    def fit(self) -> Ray:
        """Get the GEM per SKR settlement price.

        Returns:
            The GEM per SKR settlement (kill) price.
        """
        return Ray(self._contractTub.call().fit())

    def rho(self) -> int:
        """Get the time of the last drip.

        Returns:
            The time of the last drip as a unix timestamp.
        """
        return self._contractTub.call().rho()

    def tau(self) -> int:
        """Get the time of the last prod.

        Returns:
            The time of the last prod as a unix timestamp.
        """
        return self._contractTip.call().tau()

    def chi(self) -> Ray:
        """Get the internal debt price.

        Every invocation of this method calls `drip()` internally, so the value you receive is always up-to-date.
        But as calling it doesn't result in an Ethereum transaction, the actual `_chi` value in the smart
        contract storage does not get updated.

        Returns:
            The internal debt price in SAI.
        """
        return Ray(self._contractTub.call().chi())

    def chop(self, new_axe: Ray) -> Optional[Receipt]:
        """Update the liquidation penalty.

        Args:
            new_axe: The new value of the liquidation penalty (`axe`). `1.0` means no penalty. `1.2` means 20% penalty.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_axe, Ray)
        return self._transact(self.web3, f"Tub('{self.address}').chop('{new_axe}')",
                              lambda: self._contractTub.transact().chop(new_axe.value))

    def cork(self, new_hat: Wad) -> Optional[Receipt]:
        """Update the debt ceiling.

        Args:
            new_hat: The new value of the debt ceiling (`hat`), in SAI.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_hat, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').cork('{new_hat}')",
                              lambda: self._contractTub.transact().cork(new_hat.value))

    def cuff(self, new_mat: Ray) -> Optional[Receipt]:
        """Update the liquidation ratio.

        Args:
            new_mat: The new value of the liquidation ratio (`mat`). `1.5` means the liquidation ratio is 150%.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_mat, Ray)
        return self._transact(self.web3, f"Tub('{self.address}').cuff('{new_mat}')",
                              lambda: self._contractTub.transact().cuff(new_mat.value))

    def crop(self, new_tax: Ray) -> Optional[Receipt]:
        """Update the stability fee.

        Args:
            new_tax: The new per-second value of the stability fee (`tax`). `1.0` means no stability fee.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_tax, Ray)
        return self._transact(self.web3, f"Tub('{self.address}').crop('{new_tax}')",
                              lambda: self._contractTub.transact().crop(new_tax.value))

    def coax(self, new_way: Ray) -> Optional[Receipt]:
        """Update the holder fee.

        Args:
            new_tax: The new per-second value of the holder fee (`way`). `1.0` means no holder fee.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_way, Ray)
        return self._transact(self.web3, f"Tip('{self._contractTub.call().tip()}').coax('{new_way}')",
                              lambda: self._contractTip.transact().coax(new_way.value))

    def drip(self) -> Optional[Receipt]:
        """Recalculate the internal debt price (`chi`).

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        return self._transact(self.web3, f"Tub('{self.address}').drip()",
                              lambda: self._contractTub.transact().drip())

    def prod(self) -> Optional[Receipt]:
        """Recalculate the accrued holder fee (`par`).

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        return self._transact(self.web3, f"Tip('{self._contractTub.call().tip()}').prod()",
                              lambda: self._contractTip.transact().prod())

    def ice(self) -> Wad:
        """Get the amount of good debt.

        Returns:
            The amount of good debt in SAI.
        """
        return Wad(self._contractTub.call().ice())

    def pie(self) -> Wad:
        """Get the amount of raw collateral.

        Returns:
            The amount of raw collateral in GEM.
        """
        return Wad(self._contractTub.call().pie())

    def air(self) -> Wad:
        """Get the amount of backing collateral.

        Returns:
            The amount of backing collateral in SKR.
        """
        return Wad(self._contractTub.call().air())

    def tag(self) -> Wad:
        """Get the reference price (REF per SKR).

        The price is read from the price feed (`tip()`) every time this method gets called.
        Its value is actually the value from the feed (REF per GEM) multiplied by `per()` (GEM per SKR).

        Returns:
            The reference price (REF per SKR).
        """
        return Wad(self._contractJar.call().tag())

    def par(self) -> Wad:
        """Get the accrued holder fee (REF per SAI).

        Every invocation of this method calls `prod()` internally, so the value you receive is always up-to-date.
        But as calling it doesn't result in an Ethereum transaction, the actual `_par` value in the smart
        contract storage does not get updated.

        Returns:
            The accrued holder fee.
        """
        return Wad(self._contractTip.call().par())

    def per(self) -> Ray:
        """Get the current average entry/exit price (GEM per SKR).

        In order to get the price that will be actually used on `join()` or `exit()`, see
        `jar_ask()` and `jar_bid()` respectively.

        Returns:
            The current GEM per SKR price.
        """
        return Ray(self._contractJar.call().per())

    # TODO these prefixed methods are ugly, the ultimate solution would be to have a class per smart contract
    def jar_gap(self) -> Wad:
        """Get the current spread for `join` and `exit`.

        Returns:
            The current spread for `join` and `exit`. `1.0` means no spread, `1.01` means 1% spread.
        """
        return Wad(self._contractJar.call().gap())

    # TODO these prefixed methods are ugly, the ultimate solution would be to have a class per smart contract
    def jar_jump(self, new_gap: Wad) -> Optional[Receipt]:
        """Update the current spread (`gap`) for `join` and `exit`.

        Args:
            new_tax: The new value of the spread (`gap`). `1.0` means no spread, `1.01` means 1% spread.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(new_gap, Wad)
        return self._transact(self.web3, f"Jar('{self._contractTub.call().jar()}').jump('{new_gap}')",
                              lambda: self._contractJar.transact().jump(new_gap.value))

    # TODO these prefixed methods are ugly, the ultimate solution would be to have a class per smart contract
    def jar_bid(self) -> Ray:
        """Get the current `exit()` price (GEM per SKR).

        Returns:
            The GEM per SKR price that will be used on `exit()`.
        """
        return Ray(self._contractJar.call().bid())

    # TODO these prefixed methods are ugly, the ultimate solution would be to have a class per smart contract
    def jar_ask(self) -> Ray:
        """Get the current `join()` price (GEM per SKR).

        Returns:
            The GEM per SKR price that will be used on `join()`.
        """
        return Ray(self._contractJar.call().ask())

    def cupi(self) -> int:
        """Get the last cup id

        Returns:
            The id of the last cup created. Zero if no cups have been created so far.
        """
        return self._contractTub.call().cupi()

    def cups(self, cup_id: int) -> Cup:
        """Get the cup details.

        Args:
            cup_id: Id of the cup to get the details of.

        Returns:
            Class encapsulating cup details.
        """
        assert isinstance(cup_id, int)
        array = self._contractTub.call().cups(int_to_bytes32(cup_id))
        return Cup(cup_id, Address(array[0]), Wad(array[1]), Wad(array[2]))

    def tab(self, cup_id: int) -> Wad:
        """Get the amount of debt in a cup.

        Args:
            cup_id: Id of the cup.

        Returns:
            Amount of debt in the cup, in SAI.
        """
        assert isinstance(cup_id, int)
        return Wad(self._contractTub.call().tab(int_to_bytes32(cup_id)))

    def ink(self, cup_id: int) -> Wad:
        """Get the amount of SKR collateral locked in a cup.

        Args:
            cup_id: Id of the cup.

        Returns:
            Amount of SKR collateral locked in the cup, in SKR.
        """
        assert isinstance(cup_id, int)
        return Wad(self._contractTub.call().ink(int_to_bytes32(cup_id)))

    def lad(self, cup_id: int) -> Address:
        """Get the owner of a cup.

        Args:
            cup_id: Id of the cup.

        Returns:
            Address of the owner of the cup.
        """
        assert isinstance(cup_id, int)
        return Address(self._contractTub.call().lad(int_to_bytes32(cup_id)))

    def safe(self, cup_id: int) -> bool:
        """Determine if a cup is safe.

        Args:
            cup_id: Id of the cup

        Returns:
            `True` if the cup is safe. `False` otherwise.
        """
        assert isinstance(cup_id, int)
        return self._contractTub.call().safe(int_to_bytes32(cup_id))

    def join(self, amount_in_gem: Wad) -> Optional[Receipt]:
        """Buy SKR for GEMs.

        Args:
            amount_in_gem: The amount of GEMs to buy SKR for.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(amount_in_gem, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').join('{amount_in_gem}')",
                              lambda: self._contractTub.transact().join(amount_in_gem.value))

    def join_calldata(self, amount_in_gem: Wad) -> Calldata:
        return Calldata(self.web3.eth.contract(abi=self.abiTub).encodeABI('join', [amount_in_gem]))

    def exit(self, amount_in_skr: Wad) -> Optional[Receipt]:
        """Sell SKR for GEMs.

        Args:
            amount_in_skr: The amount of SKR to sell for GEMs.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(amount_in_skr, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').exit('{amount_in_skr}')",
                              lambda: self._contractTub.transact().exit(amount_in_skr.value))

    def exit_calldata(self, amount_in_skr: Wad) -> Calldata:
        return Calldata(self.web3.eth.contract(abi=self.abiTub).encodeABI('exit', [amount_in_skr]))

    #TODO make it return the id of the newly created cup
    def open(self) -> Optional[Receipt]:
        """Create a new cup.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        return self._transact(self.web3, f"Tub('{self.address}').open()",
                              lambda: self._contractTub.transact().open())

    def shut(self, cup_id: int) -> Optional[Receipt]:
        """Close a cup.

        Involves calling `wipe()` and `free()` internally in order to clear all remaining SAI debt and free
        all remaining SKR collateral.

        Args:
            cup_id: Id of the cup to close.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        return self._transact(self.web3, f"Tub('{self.address}').shut('{cup_id}')",
                              lambda: self._contractTub.transact().shut(int_to_bytes32(cup_id)))

    def lock(self, cup_id: int, amount_in_skr: Wad) -> Optional[Receipt]:
        """Post additional SKR collateral to a cup.

        Args:
            cup_id: Id of the cup to post the collateral into.
            amount_in_skr: The amount of collateral to post, in SKR.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        assert isinstance(amount_in_skr, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').lock('{cup_id}', '{amount_in_skr}')",
                              lambda: self._contractTub.transact().lock(int_to_bytes32(cup_id), amount_in_skr.value))

    def free(self, cup_id: int, amount_in_skr: Wad) -> Optional[Receipt]:
        """Remove excess SKR collateral from a cup.

        Args:
            cup_id: Id of the cup to remove the collateral from.
            amount_in_skr: The amount of collateral to remove, in SKR.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        assert isinstance(amount_in_skr, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').free('{cup_id}', '{amount_in_skr}')",
                              lambda: self._contractTub.transact().free(int_to_bytes32(cup_id), amount_in_skr.value))

    def draw(self, cup_id: int, amount_in_sai: Wad) -> Optional[Receipt]:
        """Issue the specified amount of SAI stablecoins.

        Args:
            cup_id: Id of the cup to issue the SAI from.
            amount_in_sai: The amount SAI to be issued.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        assert isinstance(amount_in_sai, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').draw('{cup_id}', '{amount_in_sai}')",
                              lambda: self._contractTub.transact().draw(int_to_bytes32(cup_id), amount_in_sai.value))

    def wipe(self, cup_id: int, amount_in_sai: Wad) -> Optional[Receipt]:
        """Repay some portion of existing SAI debt.

        Args:
            cup_id: Id of the cup to repay the SAI to.
            amount_in_sai: The amount SAI to be repaid.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        assert isinstance(amount_in_sai, Wad)
        return self._transact(self.web3, f"Tub('{self.address}').wipe('{cup_id}', '{amount_in_sai}')",
                              lambda: self._contractTub.transact().wipe(int_to_bytes32(cup_id), amount_in_sai.value))

    def give(self, cup_id: int, new_lad: Address) -> Optional[Receipt]:
        """Transfer ownership of a cup.

        Args:
            cup_id: Id of the cup to transfer ownership of.
            new_lad: New owner of the cup.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        assert isinstance(new_lad, Address)
        return self._transact(self.web3, f"Tub('{self.address}').give('{cup_id}', '{new_lad}')",
                              lambda: self._contractTub.transact().give(int_to_bytes32(cup_id), new_lad.address))

    def bite(self, cup_id: int) -> Optional[Receipt]:
        """Initiate liquidation of an undercollateralized cup.

        Args:
            cup_id: Id of the cup to liquidate.

        Returns:
            A `Receipt` if the Ethereum transaction was successful.
            `None` if the Ethereum transaction failed.
        """
        assert isinstance(cup_id, int)
        return self._transact(self.web3, f"Tub('{self.address}').bite('{cup_id}')",
                              lambda: self._contractTub.transact().bite(int_to_bytes32(cup_id)))

    def __eq__(self, other):
        assert(isinstance(other, Tub))
        return self.address == other.addressTub

    def __repr__(self):
        return f"Tub('{self.address}')"
Exemplo n.º 9
0
class DSValue(Contract):
    """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 `poke()` and cleared using `void()`.

    Everybody can read from a `DSValue`.
    Calling `poke()` and `void()` is usually whitelisted to some addresses only.

    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, *args):
        return DSValue(web3=web3,
                       address=Contract._deploy(web3, DSValue.abi, DSValue.bin,
                                                args))

    def __init__(self, web3: Web3, address: Address):
        self.web3 = web3
        self.address = address
        self._assert_contract_exists(web3, address)
        self._contract = web3.eth.contract(abi=self.abi)(
            address=address.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.call().peek()[1]

    def read(self) -> bytes:
        """Reads the current value from this instance as a byte array.

        If this instance does not contain a value, throws an exception.

        Returns:
            A 32-byte array with the current value of this instance.
        """
        read_value = self._contract.call().read()
        return array.array('B', [ord(x) for x in read_value]).tostring()

    def read_as_hex(self) -> str:
        """Reads the current value from this instance and converts it to a hex string.

        If this instance does not contain a value, throws an exception.

        Returns:
            A string with a hexadecimal representation of the current value of this instance.
        """
        return ''.join(hex(x)[2:].zfill(2) for x in self.read())

    def read_as_int(self) -> int:
        """Reads the current value from this instance and converts it to an int.

        If the value is actually a `Ray` or a `Wad`, you can convert it to one using `Ray(...)`
        or `Wad(...)`. Please see `Ray` or `Wad` for more details.

        If this instance does not contain a value, throws an exception.

        Returns:
            An integer representation of the current value of this instance.
        """
        return int(self.read_as_hex(), 16)

    def poke(self, new_value: bytes) -> Optional[Receipt]:
        """Populates this instance with a new value.

        Args:
            new_value: A 32-byte array with the new value to be set.

        Returns:
            A `Receipt` if the Ethereum transaction was successful and the value has been set.
            `None` if the Ethereum transaction failed.
        """
        assert (isinstance(new_value, bytes))
        assert (len(new_value) == 32)
        return self._transact(
            self.web3, f"DSValue('{self.address}').poke('{new_value}')",
            lambda: self._contract.transact().poke(new_value))

    def poke_with_int(self, new_value: int) -> Optional[Receipt]:
        """Populates this instance with a new value.

        Handles the conversion of a Python `int` into the Solidity `bytes32` type automatically.

        If the value you want to set is actually a `Ray` or a `Wad`, you can get the integer value from them
        by accessing their `value` property. Please see `Ray` or `Wad` for more details.

        Args:
            new_value: A non-negative integer with the new value to be set.

        Returns:
            A `Receipt` if the Ethereum transaction was successful and the value has been set.
            `None` if the Ethereum transaction failed.
        """
        assert (isinstance(new_value, int))
        assert (new_value >= 0)
        return self.poke(new_value.to_bytes(32, byteorder='big'))

    def void(self) -> Optional[Receipt]:
        """Removes the current value from this instance.

        Returns:
            A `Receipt` if the Ethereum transaction was successful and the value has been removed.
            `None` if the Ethereum transaction failed.
        """
        return self._transact(self.web3, f"DSValue('{self.address}').void()",
                              lambda: self._contract.transact().void())
Exemplo n.º 10
0
class TxManager(Contract):
    """A client for the `TxManager` contract.

    `TxManager` allows to invoke multiple smart 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`.

    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):
        self.web3 = web3
        self.address = address
        self._assert_contract_exists(web3, address)
        self._contract = web3.eth.contract(abi=self.abi)(
            address=address.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):
        for token in tokens:
            approval_function(token, self.address, 'TxManager')

    def owner(self) -> Address:
        return Address(self._contract.call().owner())

    def execute(self, tokens: List[Address],
                invocations: List[Invocation]) -> Optional[Receipt]:
        """Executes multiple smart 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 (smart contract methods) to be executed.
        """
        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 self._transact(
            self.web3,
            f"TxManager('{self.address}').execute('0x...', '0x....')",
            lambda: self._contract.transact().execute(token_addresses(),
                                                      script()))