Contract Name:
FeeSplitter
Contract Source Code:
File 1 of 1 : contracts/FeeSplitter.vy
{{
"language": "Vyper",
"sources": {
".venv/lib/python3.12/site-packages/snekmate/auth/ownable.vy": {
"content": "# pragma version ~=0.4.0\n\"\"\"\n@title Owner-Based Access Control Functions\n@custom:contract-name ownable\n@license GNU Affero General Public License v3.0 only\n@author pcaversaccio\n@notice These functions can be used to implement a basic access\n control mechanism, where there is an account (an owner)\n that can be granted exclusive access to specific functions.\n By default, the owner account will be the one that deploys\n the contract. This can later be changed with `transfer_ownership`.\n An exemplary integration can be found in the ERC-20 implementation here:\n https://github.com/pcaversaccio/snekmate/blob/main/src/snekmate/tokens/erc20.vy.\n The implementation is inspired by OpenZeppelin's implementation here:\n https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol.\n\"\"\"\n\n\n# @dev Returns the address of the current owner.\n# @notice If you declare a variable as `public`,\n# Vyper automatically generates an `external`\n# getter function for the variable.\nowner: public(address)\n\n\n# @dev Emitted when the ownership is transferred\n# from `previous_owner` to `new_owner`.\nevent OwnershipTransferred:\n previous_owner: indexed(address)\n new_owner: indexed(address)\n\n\n@deploy\n@payable\ndef __init__():\n \"\"\"\n @dev To omit the opcodes for checking the `msg.value`\n in the creation-time EVM bytecode, the constructor\n is declared as `payable`.\n @notice The `owner` role will be assigned to\n the `msg.sender`.\n \"\"\"\n self._transfer_ownership(msg.sender)\n\n\n@external\ndef transfer_ownership(new_owner: address):\n \"\"\"\n @dev Transfers the ownership of the contract\n to a new account `new_owner`.\n @notice Note that this function can only be\n called by the current `owner`. Also,\n the `new_owner` cannot be the zero address.\n @param new_owner The 20-byte address of the new owner.\n \"\"\"\n self._check_owner()\n assert new_owner != empty(address), \"ownable: new owner is the zero address\"\n self._transfer_ownership(new_owner)\n\n\n@external\ndef renounce_ownership():\n \"\"\"\n @dev Leaves the contract without an owner.\n @notice Renouncing ownership will leave the\n contract without an owner, thereby\n removing any functionality that is\n only available to the owner.\n \"\"\"\n self._check_owner()\n self._transfer_ownership(empty(address))\n\n\n@internal\ndef _check_owner():\n \"\"\"\n @dev Throws if the sender is not the owner.\n \"\"\"\n assert msg.sender == self.owner, \"ownable: caller is not the owner\"\n\n\n@internal\ndef _transfer_ownership(new_owner: address):\n \"\"\"\n @dev Transfers the ownership of the contract\n to a new account `new_owner`.\n @notice This is an `internal` function without\n access restriction.\n @param new_owner The 20-byte address of the new owner.\n \"\"\"\n old_owner: address = self.owner\n self.owner = new_owner\n log OwnershipTransferred(old_owner, new_owner)\n",
"sha256sum": "88ae32cf8b3e4a332d6518256019193419150e7ff716dd006a8d471550c329fc"
},
"contracts/interfaces/IControllerFactory.vyi": {
"content": "@external\n@view\ndef controllers(index: uint256) -> address:\n ...\n\n\n@external\n@view\ndef n_collaterals() -> uint256:\n ...\n",
"sha256sum": "80ca3e3c4313fc157183a693b93433109589aaf1084dfc4d8ccf890fe737c216"
},
"contracts/interfaces/IController.vyi": {
"content": "@external\ndef collect_fees() -> uint256:\n ...\n",
"sha256sum": "99e7b55be092ba692ed2a2732ea70792f965f65278729d3e368e8166696363c4"
},
"contracts/ControllerMulticlaim.vy": {
"content": "# pragma version ~=0.4.0\n\n\"\"\"\n@title ControllerMulticlaim\n@notice Helper module to claim fees from multiple\ncontrollers at the same time.\n@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved\n@author curve.fi\n@custom:security security@curve.fi\n\"\"\"\n\nfrom contracts.interfaces import IControllerFactory\nfrom contracts.interfaces import IController\n\nfactory: immutable(IControllerFactory)\n\nallowed_controllers: public(HashMap[IController, bool])\ncontrollers: public(DynArray[IController, MAX_CONTROLLERS])\n\n# maximum number of claims in a single transaction\nMAX_CONTROLLERS: constant(uint256) = 50\n\n\n@deploy\ndef __init__(_factory: IControllerFactory):\n assert _factory.address != empty(address), \"zeroaddr: factory\"\n\n factory = _factory\n\n\ndef claim_controller_fees(controllers: DynArray[IController, MAX_CONTROLLERS]):\n \"\"\"\n @notice Claims admin fees from a list of controllers.\n @param controllers The list of controllers to claim fees from.\n @dev For the claim to succeed, the controller must be in the list of\n allowed controllers. If the list of controllers is empty, all\n controllers in the factory are claimed from.\n \"\"\"\n if len(controllers) == 0:\n for c: IController in self.controllers:\n extcall c.collect_fees()\n else:\n for c: IController in controllers:\n if not self.allowed_controllers[c]:\n raise \"controller: not in factory\"\n extcall c.collect_fees()\n\n\n@nonreentrant\n@external\ndef update_controllers():\n \"\"\"\n @notice Update the list of controllers so that it corresponds to the\n list of controllers in the factory.\n @dev The list of controllers can only add new controllers from the\n factory when updated.\n \"\"\"\n old_len: uint256 = len(self.controllers)\n new_len: uint256 = staticcall factory.n_collaterals()\n for i: uint256 in range(old_len, new_len, bound=MAX_CONTROLLERS):\n c: IController = IController(staticcall factory.controllers(i))\n self.allowed_controllers[c] = True\n self.controllers.append(c)\n\n\n@view\n@external\ndef n_controllers() -> uint256:\n return len(self.controllers)\n",
"sha256sum": "364aa68720820361a472d75c06d1bcaddeb815e79910564f6627238abada4fc1"
},
"contracts/FeeSplitter.vy": {
"content": "# pragma version ~=0.4.0\n\n\"\"\"\n@title FeeSplitter\n@notice A contract that collects fees from multiple crvUSD controllers\nin a single transaction and distributes them according to some weights.\n@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved\n@author curve.fi\n@custom:security security@curve.fi\n\"\"\"\n\nfrom ethereum.ercs import IERC20\nfrom ethereum.ercs import IERC165\n\nfrom snekmate.auth import ownable\ninitializes: ownable\nexports: (\n ownable.transfer_ownership,\n ownable.renounce_ownership,\n ownable.owner\n)\n\nimport ControllerMulticlaim as multiclaim\ninitializes: multiclaim\nexports: (\n multiclaim.update_controllers,\n multiclaim.n_controllers,\n multiclaim.allowed_controllers,\n multiclaim.controllers\n)\n\n\nevent SetReceivers: pass\nevent LivenessProtectionTriggered: pass\n\n\nevent FeeDispatched:\n receiver: indexed(address)\n weight: uint256\n\n\nstruct Receiver:\n addr: address\n weight: uint256\n\n\nversion: public(constant(String[8])) = \"0.1.0\" # no guarantees on abi stability\n\n# maximum number of splits\nMAX_RECEIVERS: constant(uint256) = 100\n# maximum basis points (100%)\nMAX_BPS: constant(uint256) = 10_000\nDYNAMIC_WEIGHT_EIP165_ID: constant(bytes4) = 0xA1AAB33F\n\n# receiver logic\nreceivers: public(DynArray[Receiver, MAX_RECEIVERS])\n\ncrvusd: immutable(IERC20)\n\n\n@deploy\ndef __init__(\n _crvusd: IERC20,\n _factory: multiclaim.IControllerFactory,\n receivers: DynArray[Receiver, MAX_RECEIVERS],\n owner: address,\n):\n \"\"\"\n @notice Contract constructor\n @param _crvusd The address of the crvUSD token contract\n @param _factory The address of the crvUSD controller factory\n @param receivers The list of receivers (address, weight).\n Last item in the list is the excess receiver by default.\n @param owner The address of the contract owner\n \"\"\"\n assert _crvusd.address != empty(address), \"zeroaddr: crvusd\"\n assert owner != empty(address), \"zeroaddr: owner\"\n\n ownable.__init__()\n ownable._transfer_ownership(owner)\n multiclaim.__init__(_factory)\n\n # setting immutables\n crvusd = _crvusd\n\n # set the receivers\n self._set_receivers(receivers)\n\n\ndef _is_dynamic(addr: address) -> bool:\n \"\"\"\n This function covers the following cases without reverting:\n 1. The address is an EIP-165 compliant contract that supports\n the dynamic weight interface (returns True).\n 2. The address is a contract that does not comply to EIP-165\n (returns False).\n 3. The address is an EIP-165 compliant contract that does not\n support the dynamic weight interface (returns False).\n 4. The address is an EOA (returns False).\n \"\"\"\n success: bool = False\n response: Bytes[32] = b\"\"\n success, response = raw_call(\n addr,\n abi_encode(\n DYNAMIC_WEIGHT_EIP165_ID,\n method_id=method_id(\"supportsInterface(bytes4)\"),\n ),\n max_outsize=32,\n is_static_call=True,\n revert_on_failure=False,\n )\n return success and convert(response, bool)\n\n\ndef _get_dynamic_weight(addr: address) -> uint256:\n success: bool = False\n response: Bytes[32] = b\"\"\n success, response = raw_call(\n addr,\n method_id(\"weight()\"),\n max_outsize=32,\n is_static_call=True,\n revert_on_failure=False,\n )\n\n if success:\n return convert(response, uint256)\n else:\n # ! DANGER !\n # If we got here something went wrong. This condition\n # is here to preserve liveness but it also means that\n # a receiver is not getting any money.\n # ! DANGER !\n log LivenessProtectionTriggered()\n\n return 0\n\n\n\n\ndef _set_receivers(receivers: DynArray[Receiver, MAX_RECEIVERS]):\n assert len(receivers) > 0, \"receivers: empty\"\n total_weight: uint256 = 0\n for r: Receiver in receivers:\n assert r.addr != empty(address), \"zeroaddr: receivers\"\n assert r.weight > 0 and r.weight <= MAX_BPS, \"receivers: invalid weight\"\n total_weight += r.weight\n assert total_weight == MAX_BPS, \"receivers: total weight != MAX_BPS\"\n\n self.receivers = receivers\n\n log SetReceivers()\n\n\n@nonreentrant\n@external\ndef dispatch_fees(\n controllers: DynArray[\n multiclaim.IController, multiclaim.MAX_CONTROLLERS\n ] = []\n):\n \"\"\"\n @notice Claim fees from all controllers and distribute them\n @param controllers The list of controllers to claim fees from (default: all)\n @dev Splits and transfers the balance according to the receivers weights\n \"\"\"\n\n multiclaim.claim_controller_fees(controllers)\n\n balance: uint256 = staticcall crvusd.balanceOf(self)\n\n excess: uint256 = 0\n\n # by iterating over the receivers, rather than the indices,\n # we avoid an oob check at every iteration.\n i: uint256 = 0\n for r: Receiver in self.receivers:\n weight: uint256 = r.weight\n\n if self._is_dynamic(r.addr):\n dynamic_weight: uint256 = self._get_dynamic_weight(r.addr)\n\n # `weight` acts as a cap to the dynamic weight, preventing\n # receivers to ask for more than what they are allowed to.\n if dynamic_weight < weight:\n excess += weight - dynamic_weight\n weight = dynamic_weight\n\n # if we're at the last iteration, it means `r` is the excess\n # receiver, therefore we add the excess to its weight.\n if i == len(self.receivers) - 1:\n weight += excess\n\n # precision loss can lead to a negligible amount of\n # dust to be left in the contract after this transfer\n extcall crvusd.transfer(r.addr, balance * weight // MAX_BPS)\n\n log FeeDispatched(r.addr, weight)\n i += 1\n\n\n@external\ndef set_receivers(receivers: DynArray[Receiver, MAX_RECEIVERS]):\n \"\"\"\n @notice Set the receivers, the last one is the excess receiver.\n @param receivers The new receivers's list.\n @dev The excess receiver is always the last element in the\n `self.receivers` array.\n \"\"\"\n ownable._check_owner()\n\n self._set_receivers(receivers)\n\n\n@view\n@external\ndef excess_receiver() -> address:\n \"\"\"\n @notice Get the excess receiver, that is the receiver\n that, on top of his weight, will receive an additional\n weight if other receivers (with a dynamic weight) ask\n for less than their cap.\n @return The address of the excess receiver.\n \"\"\"\n receivers_length: uint256 = len(self.receivers)\n return self.receivers[receivers_length - 1].addr\n\n\n@view\n@external\ndef n_receivers() -> uint256:\n \"\"\"\n @notice Get the number of receivers\n @return The number of receivers\n \"\"\"\n return len(self.receivers)\n",
"sha256sum": "646c9551c27e35f60e45969329ff2ca4a467e6aac9065a679b9aeb715442f83d"
}
},
"settings": {
"outputSelection": {
"contracts/FeeSplitter.vy": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
},
"search_paths": [
".venv/lib/python3.12/site-packages",
"."
]
},
"compiler_version": "v0.4.0+commit.e9db8d9",
"integrity": "56c34c1e23241f2f1c8b3deb6f958c671e8d56dba0a56b244c842b76c1208e13"
}}