Forkchoice Ethereum Mainnet

Address Contract Partially Verified

Address 0x2dFd89449faff8a532790667baB21cF733C064f2
Balance 0 ETH
Nonce 1
Code Size 3650 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

3650 bytes
0x5f3560e01c6002600f820660011b610de401601e395f51565b63f0350c0481186100ff57602436103417610de0576004358060a01c610de0576101005261004461074d565b610100516100ee576020806101a0526026610120527f6f776e61626c653a206e6577206f776e657220697320746865207a65726f2061610140527f646472657373000000000000000000000000000000000000000000000000000061016052610120816101a00160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a0610180528060040161019cfd5b610100516040526100fd6107ce565b005b6304783ee4811861074957602436103417610de0576004358060a01c610de05760405260016040516020525f5260405f205460605260206060f35b63b15e13ee81186107495734610de05761015261074d565b5f60405261015e6107ce565b005b638da5cb5b81186107495734610de0575f5460405260206040f35b6344ba5664811861029b5734610de0575f5c600114610de05760015f5d6002546040526020610e025f395f516312397fa1608052602060806004609c845afa6101c6573d5f5f3e3d5ffd5b60203d10610de057608090505160605260405180606051808311610de057828103905060328111610de0578015610293578101905b806080526020610e025f395f5163e94b0dd260c05260805160e052602060c0602460dc845afa61022d573d5f5f3e3d5ffd5b3d602081183d60201002188060c00160e011610de05760c0518060a01c610de057610100525061010090505160a0526001600160a0516020525f5260405f205560025460318111610de05760a051816003015560018101600255506001018181186101fb575b5050505f5f5d005b632e33f75181186107495734610de05760355460405260405160018103818111610de0579050603554811015610de05760011b6036015460605260206060f35b631d74182581186102f75734610de05760025460405260206040f35b631501e4d3811861074957602436103417610de0576004356004016032813511610de05780355f8160328111610de057801561035557905b8060051b6020850101358060a01c610de0578160051b6107a0015260010181811861032f575b5050806107805250505b5f5c600114610de05760015f5d6107805160208160051b018061078060405e5050610388610805565b6020610e225f395f516370a08231610e005230610e20526020610e006024610e1c845afa6103b8573d5f5f3e3d5ffd5b60203d10610de057610e00905051610de052604036610e00375f60355460648111610de057801561058c57905b8060011b6036018054610e40526001810154610e605250610e6051610e8052610e4051604052610416610ea0610964565b610ea0511561047c57610e4051604052610431610ee0610a11565b610ee051610ec052610e8051610ec051101561047c57610e0051610e8051610ec051808203828111610de05790509050808201828110610de05790509050610e0052610ec051610e80525b60355460018103818111610de0579050610e2051186104b057610e8051610e0051808201828110610de05790509050610e80525b6020610e225f395f5163a9059cbb610ea052610e4051610ec052610de051610e8051808202811583838304141715610de0579050905061271081049050610ee0526020610ea06044610ebc5f855af161050b573d5f5f3e3d5ffd5b3d602081183d602010021880610ea001610ec011610de057610ea0518060011c610de057610f005250610f005050610e40517f3ec7c36ff485aa9a27938503e3094604652d1f7262464127fb79577970abe12a610e8051610ea0526020610ea0a2610e205160018101818110610de0579050610e20526001018181186103e5575b50505f5f5d005b63e94b0dd2811861074957602436103417610de057600435600254811015610de0576003015460405260206040f35b63b829511181186105dc5734610de0575f6107805261035f565b63632838bc81186107495734610de05760355460405260206040f35b6330ffa04e811861074957602436103417610de0576004356004016064813511610de05780355f8160648111610de057801561066457905b8060061b60208501018160061b611aa00181358060a01c610de0578152602082013560208201525050600101818118610630575b505080611a8052505061067561074d565b611a805160208160061b0180611a8060405e5050610691610ad9565b005b6354fd4d5081186107495734610de05760208060805260056040527f302e312e3000000000000000000000000000000000000000000000000000000060605260408160800160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506080f35b63bfd772fc811861074957602436103417610de057600435603554811015610de05760011b603601805460405260018101546060525060406040f35b5f5ffd5b5f543318156107cc5760208060a05260206040527f6f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260605260408160a00160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a060805280600401609cfd5b565b5f546060526040515f556040516060517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f6080a3565b60405161086f575f60025460328111610de057801561086857905b80600301546106a0526106a051631e0cfcef6106c05260206106c060046106dc5f855af1610850573d5f5f3e3d5ffd5b60203d10610de0576106c05050600101818118610820575b5050610962565b5f60405160328111610de057801561095f57905b8060051b606001516106a05260016106a0516020525f5260405f20546109205760208061072052601a6106c0527f636f6e74726f6c6c65723a206e6f7420696e20666163746f72790000000000006106e0526106c0816107200160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a0610700528060040161071cfd5b6106a051631e0cfcef6106c05260206106c060046106dc5f855af1610947573d5f5f3e3d5ffd5b60203d10610de0576106c05050600101818118610883575b50505b565b6040366060376040515a6301ffc9a760c45260047fa1aab33f0000000000000000000000000000000000000000000000000000000060e45260200160c05260c050602061014060c05160e08585fa90509050610160523d602081183d602010021861012052610120602081510180826101805e505061016051606052602061018051018061018060805e506060516109fc575f610a0c565b60a05160805160200360031b1c15155b815250565b6040366060376040515a600460c0527fa1aab33f0000000000000000000000000000000000000000000000000000000060e05260c050602061012060c05160e08585fa90509050610140523d602081183d602010021861010052610100602081510180826101605e505061014051606052602061016051018061016060805e50606051610ac6577f7544693205d94fae4fc2b20449536d0486b497d16e6d7dcbaf967b8fc277d02c5f60c0a15f815250610ad7565b60a05160805160200360031b1c8152505b565b604051610b5d576020806119c0526010611960527f7265636569766572733a20656d7074790000000000000000000000000000000061198052611960816119c00160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a06119a052806004016119bcfd5b5f611960525f60405160648111610de0578015610cc957905b8060061b6060016040816119805e5061198051610c0a57602080611a205260136119c0527f7a65726f616464723a20726563656976657273000000000000000000000000006119e0526119c081611a200160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a0611a005280600401611a1cfd5b6119a05115610c21576127106119a0511115610c23565b5f5b610ca457602080611a205260196119c0527f7265636569766572733a20696e76616c696420776569676874000000000000006119e0526119c081611a200160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a0611a005280600401611a1cfd5b611960516119a051808201828110610de0579050905061196052600101818118610b76575b5050612710611960511815610d7a57602080611a00526022611980527f7265636569766572733a20746f74616c2077656967687420213d204d41585f426119a0527f50530000000000000000000000000000000000000000000000000000000000006119c05261198081611a000160208251018083835e508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506308c379a06119e052806004016119fcfd5b60405160208160061b015f81601f0160051c60c98111610de0578015610db457905b8060051b604001518160350155600101818118610d9c575b505050507fcef5c0f74b8e26cac8a85442177d7e9b9792cc2b2627efce6a5c3d764fc34df15f611980a1565b5f80fd016005f80749013a0749017b0593070d05c20018074902db074907490693000000000000000000000000c9332fdcb1c491dcc683bae86fe3cb70360738bc000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e

Verified Source Code Partial Match

Compiler: v0.4.0+commit.e9db8d9f
ownable.vy 93 lines
# pragma version ~=0.4.0
"""
@title Owner-Based Access Control Functions
@custom:contract-name ownable
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
@notice These functions can be used to implement a basic access
        control mechanism, where there is an account (an owner)
        that can be granted exclusive access to specific functions.
        By default, the owner account will be the one that deploys
        the contract. This can later be changed with `transfer_ownership`.
        An exemplary integration can be found in the ERC-20 implementation here:
        https://github.com/pcaversaccio/snekmate/blob/main/src/snekmate/tokens/erc20.vy.
        The implementation is inspired by OpenZeppelin's implementation here:
        https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol.
"""


# @dev Returns the address of the current owner.
# @notice If you declare a variable as `public`,
# Vyper automatically generates an `external`
# getter function for the variable.
owner: public(address)


# @dev Emitted when the ownership is transferred
# from `previous_owner` to `new_owner`.
event OwnershipTransferred:
    previous_owner: indexed(address)
    new_owner: indexed(address)


@deploy
@payable
def __init__():
    """
    @dev To omit the opcodes for checking the `msg.value`
         in the creation-time EVM bytecode, the constructor
         is declared as `payable`.
    @notice The `owner` role will be assigned to
            the `msg.sender`.
    """
    self._transfer_ownership(msg.sender)


@external
def transfer_ownership(new_owner: address):
    """
    @dev Transfers the ownership of the contract
         to a new account `new_owner`.
    @notice Note that this function can only be
            called by the current `owner`. Also,
            the `new_owner` cannot be the zero address.
    @param new_owner The 20-byte address of the new owner.
    """
    self._check_owner()
    assert new_owner != empty(address), "ownable: new owner is the zero address"
    self._transfer_ownership(new_owner)


@external
def renounce_ownership():
    """
    @dev Leaves the contract without an owner.
    @notice Renouncing ownership will leave the
            contract without an owner, thereby
            removing any functionality that is
            only available to the owner.
    """
    self._check_owner()
    self._transfer_ownership(empty(address))


@internal
def _check_owner():
    """
    @dev Throws if the sender is not the owner.
    """
    assert msg.sender == self.owner, "ownable: caller is not the owner"


@internal
def _transfer_ownership(new_owner: address):
    """
    @dev Transfers the ownership of the contract
         to a new account `new_owner`.
    @notice This is an `internal` function without
            access restriction.
    @param new_owner The 20-byte address of the new owner.
    """
    old_owner: address = self.owner
    self.owner = new_owner
    log OwnershipTransferred(old_owner, new_owner)
ControllerMulticlaim.vy 69 lines
# pragma version ~=0.4.0

"""
@title ControllerMulticlaim
@notice Helper module to claim fees from multiple
controllers at the same time.
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
@author curve.fi
@custom:security [email protected]
"""

from contracts.interfaces import IControllerFactory
from contracts.interfaces import IController

factory: immutable(IControllerFactory)

allowed_controllers: public(HashMap[IController, bool])
controllers: public(DynArray[IController, MAX_CONTROLLERS])

# maximum number of claims in a single transaction
MAX_CONTROLLERS: constant(uint256) = 50


@deploy
def __init__(_factory: IControllerFactory):
    assert _factory.address != empty(address), "zeroaddr: factory"

    factory = _factory


def claim_controller_fees(controllers: DynArray[IController, MAX_CONTROLLERS]):
    """
    @notice Claims admin fees from a list of controllers.
    @param controllers The list of controllers to claim fees from.
    @dev For the claim to succeed, the controller must be in the list of
        allowed controllers. If the list of controllers is empty, all
        controllers in the factory are claimed from.
    """
    if len(controllers) == 0:
        for c: IController in self.controllers:
            extcall c.collect_fees()
    else:
        for c: IController in controllers:
            if not self.allowed_controllers[c]:
                raise "controller: not in factory"
            extcall c.collect_fees()


@nonreentrant
@external
def update_controllers():
    """
    @notice Update the list of controllers so that it corresponds to the
        list of controllers in the factory.
    @dev The list of controllers can only add new controllers from the
        factory when updated.
    """
    old_len: uint256 = len(self.controllers)
    new_len: uint256 = staticcall factory.n_collaterals()
    for i: uint256 in range(old_len, new_len, bound=MAX_CONTROLLERS):
        c: IController = IController(staticcall factory.controllers(i))
        self.allowed_controllers[c] = True
        self.controllers.append(c)


@view
@external
def n_controllers() -> uint256:
    return len(self.controllers)
FeeSplitter.vy 237 lines
# pragma version ~=0.4.0

"""
@title FeeSplitter
@notice A contract that collects fees from multiple crvUSD controllers
in a single transaction and distributes them according to some weights.
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
@author curve.fi
@custom:security [email protected]
"""

from ethereum.ercs import IERC20
from ethereum.ercs import IERC165

from snekmate.auth import ownable
initializes: ownable
exports: (
    ownable.transfer_ownership,
    ownable.renounce_ownership,
    ownable.owner
)

import ControllerMulticlaim as multiclaim
initializes: multiclaim
exports: (
    multiclaim.update_controllers,
    multiclaim.n_controllers,
    multiclaim.allowed_controllers,
    multiclaim.controllers
)


event SetReceivers: pass
event LivenessProtectionTriggered: pass


event FeeDispatched:
    receiver: indexed(address)
    weight: uint256


struct Receiver:
    addr: address
    weight: uint256


version: public(constant(String[8])) = "0.1.0"  # no guarantees on abi stability

# maximum number of splits
MAX_RECEIVERS: constant(uint256) = 100
# maximum basis points (100%)
MAX_BPS: constant(uint256) = 10_000
DYNAMIC_WEIGHT_EIP165_ID: constant(bytes4) = 0xA1AAB33F

# receiver logic
receivers: public(DynArray[Receiver, MAX_RECEIVERS])

crvusd: immutable(IERC20)


@deploy
def __init__(
    _crvusd: IERC20,
    _factory: multiclaim.IControllerFactory,
    receivers: DynArray[Receiver, MAX_RECEIVERS],
    owner: address,
):
    """
    @notice Contract constructor
    @param _crvusd The address of the crvUSD token contract
    @param _factory The address of the crvUSD controller factory
    @param receivers The list of receivers (address, weight).
        Last item in the list is the excess receiver by default.
    @param owner The address of the contract owner
    """
    assert _crvusd.address != empty(address), "zeroaddr: crvusd"
    assert owner != empty(address), "zeroaddr: owner"

    ownable.__init__()
    ownable._transfer_ownership(owner)
    multiclaim.__init__(_factory)

    # setting immutables
    crvusd = _crvusd

    # set the receivers
    self._set_receivers(receivers)


def _is_dynamic(addr: address) -> bool:
    """
    This function covers the following cases without reverting:
    1. The address is an EIP-165 compliant contract that supports
        the dynamic weight interface (returns True).
    2. The address is a contract that does not comply to EIP-165
        (returns False).
    3. The address is an EIP-165 compliant contract that does not
        support the dynamic weight interface (returns False).
    4. The address is an EOA (returns False).
    """
    success: bool = False
    response: Bytes[32] = b""
    success, response = raw_call(
        addr,
        abi_encode(
            DYNAMIC_WEIGHT_EIP165_ID,
            method_id=method_id("supportsInterface(bytes4)"),
        ),
        max_outsize=32,
        is_static_call=True,
        revert_on_failure=False,
    )
    return success and convert(response, bool)


def _get_dynamic_weight(addr: address) -> uint256:
    success: bool = False
    response: Bytes[32] = b""
    success, response = raw_call(
        addr,
        method_id("weight()"),
        max_outsize=32,
        is_static_call=True,
        revert_on_failure=False,
    )

    if success:
        return convert(response, uint256)
    else:
        # ! DANGER !
        # If we got here something went wrong. This condition
        # is here to preserve liveness but it also means that
        # a receiver is not getting any money.
        # ! DANGER !
        log LivenessProtectionTriggered()

        return 0




def _set_receivers(receivers: DynArray[Receiver, MAX_RECEIVERS]):
    assert len(receivers) > 0, "receivers: empty"
    total_weight: uint256 = 0
    for r: Receiver in receivers:
        assert r.addr != empty(address), "zeroaddr: receivers"
        assert r.weight > 0 and r.weight <= MAX_BPS, "receivers: invalid weight"
        total_weight += r.weight
    assert total_weight == MAX_BPS, "receivers: total weight != MAX_BPS"

    self.receivers = receivers

    log SetReceivers()


@nonreentrant
@external
def dispatch_fees(
    controllers: DynArray[
        multiclaim.IController, multiclaim.MAX_CONTROLLERS
    ] = []
):
    """
    @notice Claim fees from all controllers and distribute them
    @param controllers The list of controllers to claim fees from (default: all)
    @dev Splits and transfers the balance according to the receivers weights
    """

    multiclaim.claim_controller_fees(controllers)

    balance: uint256 = staticcall crvusd.balanceOf(self)

    excess: uint256 = 0

    # by iterating over the receivers, rather than the indices,
    # we avoid an oob check at every iteration.
    i: uint256 = 0
    for r: Receiver in self.receivers:
        weight: uint256 = r.weight

        if self._is_dynamic(r.addr):
            dynamic_weight: uint256 = self._get_dynamic_weight(r.addr)

            # `weight` acts as a cap to the dynamic weight, preventing
            # receivers to ask for more than what they are allowed to.
            if dynamic_weight < weight:
                excess += weight - dynamic_weight
                weight = dynamic_weight

        # if we're at the last iteration, it means `r` is the excess
        # receiver, therefore we add the excess to its weight.
        if i == len(self.receivers) - 1:
            weight += excess

        # precision loss can lead to a negligible amount of
        # dust to be left in the contract after this transfer
        extcall crvusd.transfer(r.addr, balance * weight // MAX_BPS)

        log FeeDispatched(r.addr, weight)
        i += 1


@external
def set_receivers(receivers: DynArray[Receiver, MAX_RECEIVERS]):
    """
    @notice Set the receivers, the last one is the excess receiver.
    @param receivers The new receivers's list.
    @dev The excess receiver is always the last element in the
        `self.receivers` array.
    """
    ownable._check_owner()

    self._set_receivers(receivers)


@view
@external
def excess_receiver() -> address:
    """
    @notice Get the excess receiver, that is the receiver
        that, on top of his weight, will receive an additional
        weight if other receivers (with a dynamic weight) ask
        for less than their cap.
    @return The address of the excess receiver.
    """
    receivers_length: uint256 = len(self.receivers)
    return self.receivers[receivers_length - 1].addr


@view
@external
def n_receivers() -> uint256:
    """
    @notice Get the number of receivers
    @return The number of receivers
    """
    return len(self.receivers)

Read Contract

allowed_controllers 0x04783ee4 → bool
controllers 0xe94b0dd2 → address
excess_receiver 0x2e33f751 → address
n_controllers 0x1d741825 → uint256
n_receivers 0x632838bc → uint256
owner 0x8da5cb5b → address
receivers 0xbfd772fc → tuple
version 0x54fd4d50 → string

Write Contract 6 functions

These functions modify contract state and require a wallet transaction to execute.

dispatch_fees 0xb8295111
No parameters
dispatch_fees 0x1501e4d3
address[] controllers
renounce_ownership 0xb15e13ee
No parameters
set_receivers 0xcc237032
tuple[] receivers
transfer_ownership 0xf0350c04
address new_owner
update_controllers 0x44ba5664
No parameters

Recent Transactions

No transactions found for this address