Skip to content

Instantly share code, notes, and snippets.

@sakulstra
Last active April 24, 2023 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sakulstra/80109f6fabb0d64b8a85b16280ae8572 to your computer and use it in GitHub Desktop.
Save sakulstra/80109f6fabb0d64b8a85b16280ae8572 to your computer and use it in GitHub Desktop.
metis bugreport
{
"message": "OK",
"result": [
{
"ABI": "[{\"inputs\":[],\"name\":\"ETH_MOCK_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"REVISION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"streamId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"who\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"streamId\",\"type\":\"uint256\"}],\"name\":\"cancelStream\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deposit\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"startTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stopTime\",\"type\":\"uint256\"}],\"name\":\"createStream\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"streamId\",\"type\":\"uint256\"}],\"name\":\"deltaOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"delta\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getFundsAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNextStreamId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"streamId\",\"type\":\"uint256\"}],\"name\":\"getStream\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deposit\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"startTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stopTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"remainingBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"ratePerSecond\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"fundsAdmin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nextStreamId\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"}],\"name\":\"setFundsAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"streamId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdrawFromStream\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"streamId\",\"type\":\"uint256\"},{\"indexed\":true,\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"senderBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"recipientBalance\",\"type\":\"uint256\"}],\"name\":\"CancelStream\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"streamId\",\"type\":\"uint256\"},{\"indexed\":true,\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"deposit\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"tokenAddress\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"startTime\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"stopTime\",\"type\":\"uint256\"}],\"name\":\"CreateStream\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"fundsAdmin\",\"type\":\"address\"}],\"name\":\"NewFundsAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"streamId\",\"type\":\"uint256\"},{\"indexed\":true,\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"WithdrawFromStream\",\"type\":\"event\"},{\"type\":\"receive\"}]",
"CompilerSettings": {
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
]
}
},
"remappings": [
"@aave/core-v2/=lib/protocol-v2/",
"@aave/core-v3/=lib/aave-address-book/lib/aave-v3-core/",
"@aave/periphery-v3/=lib/aave-address-book/lib/aave-v3-periphery/",
"aave-address-book/=lib/aave-address-book/src/",
"aave-helpers/=lib/aave-helpers/src/",
"aave-v3-core/=lib/aave-address-book/lib/aave-v3-core/",
"aave-v3-periphery/=lib/aave-address-book/lib/aave-v3-periphery/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"protocol-v2/=lib/protocol-v2/",
"solidity-utils/=lib/solidity-utils/src/"
]
},
"CompilerVersion": "v0.8.17+commit.8df45f5f",
"ContractName": "Collector",
"EVMVersion": "london",
"FileName": "src/contracts/Collector.sol",
"IsProxy": "false",
"OptimizationRuns": 200,
"OptimizationUsed": "true",
"SourceCode": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';\nimport {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';\nimport {Address} from 'solidity-utils/contracts/oz-common/Address.sol';\nimport {ICollector} from '../interfaces/ICollector.sol';\nimport {VersionedInitializable} from '../libs/VersionedInitializable.sol';\nimport {ReentrancyGuard} from '../libs/ReentrancyGuard.sol';\n\n/**\n * @title Collector\n * @notice Stores ERC20 tokens of an ecosystem reserve and allows to dispose of them via approval\n * or transfer dynamics or streaming capabilities.\n * Modification of Sablier https://github.com/sablierhq/sablier/blob/develop/packages/protocol/contracts/Sablier.sol\n * Original can be found also deployed on https://etherscan.io/address/0xCD18eAa163733Da39c232722cBC4E8940b1D8888\n * Modifications:\n * - Sablier \"pulls\" the funds from the creator of the stream at creation. In the Aave case, we already have the funds.\n * - Anybody can create streams on Sablier. Here, only the funds admin (Aave governance via controller) can\n * - Adapted codebase to Solidity 0.8.11, mainly removing SafeMath and CarefulMath to use native safe math\n * - Same as with creation, on Sablier the `sender` and `recipient` can cancel a stream. Here, only fund admin and recipient\n * @author BGD Labs\n **/\ncontract Collector is VersionedInitializable, ICollector, ReentrancyGuard {\n using SafeERC20 for IERC20;\n using Address for address payable;\n\n /*** Storage Properties ***/\n\n /**\n * @notice Address of the current funds admin.\n */\n address internal _fundsAdmin;\n\n /**\n * @notice Current revision of the contract.\n */\n uint256 public constant REVISION = 5;\n\n /**\n * @notice Counter for new stream ids.\n */\n uint256 private _nextStreamId;\n\n /**\n * @notice The stream objects identifiable by their unsigned integer ids.\n */\n mapping(uint256 => Stream) private _streams;\n\n /// @inheritdoc ICollector\n address public constant ETH_MOCK_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n /*** Modifiers ***/\n\n /**\n * @dev Throws if the caller is not the funds admin.\n */\n modifier onlyFundsAdmin() {\n require(msg.sender == _fundsAdmin, 'ONLY_BY_FUNDS_ADMIN');\n _;\n }\n\n /**\n * @dev Throws if the caller is not the funds admin of the recipient of the stream.\n * @param streamId The id of the stream to query.\n */\n modifier onlyAdminOrRecipient(uint256 streamId) {\n require(\n msg.sender == _fundsAdmin || msg.sender == _streams[streamId].recipient,\n 'caller is not the funds admin or the recipient of the stream'\n );\n _;\n }\n\n /**\n * @dev Throws if the provided id does not point to a valid stream.\n */\n modifier streamExists(uint256 streamId) {\n require(_streams[streamId].isEntity, 'stream does not exist');\n _;\n }\n\n /*** Contract Logic Starts Here */\n\n /// @inheritdoc ICollector\n function initialize(address fundsAdmin, uint256 nextStreamId) external initializer {\n if (nextStreamId != 0) {\n _nextStreamId = nextStreamId;\n }\n\n // can be removed after first deployment\n _initGuard();\n _setFundsAdmin(fundsAdmin);\n }\n\n /*** View Functions ***/\n\n /// @inheritdoc VersionedInitializable\n function getRevision() internal pure override returns (uint256) {\n return REVISION;\n }\n\n /// @inheritdoc ICollector\n function getFundsAdmin() external view returns (address) {\n return _fundsAdmin;\n }\n\n /// @inheritdoc ICollector\n function getNextStreamId() external view returns (uint256) {\n return _nextStreamId;\n }\n\n /// @inheritdoc ICollector\n function getStream(\n uint256 streamId\n )\n external\n view\n streamExists(streamId)\n returns (\n address sender,\n address recipient,\n uint256 deposit,\n address tokenAddress,\n uint256 startTime,\n uint256 stopTime,\n uint256 remainingBalance,\n uint256 ratePerSecond\n )\n {\n sender = _streams[streamId].sender;\n recipient = _streams[streamId].recipient;\n deposit = _streams[streamId].deposit;\n tokenAddress = _streams[streamId].tokenAddress;\n startTime = _streams[streamId].startTime;\n stopTime = _streams[streamId].stopTime;\n remainingBalance = _streams[streamId].remainingBalance;\n ratePerSecond = _streams[streamId].ratePerSecond;\n }\n\n /**\n * @notice Returns either the delta in seconds between `block.timestamp` and `startTime` or\n * between `stopTime` and `startTime, whichever is smaller. If `block.timestamp` is before\n * `startTime`, it returns 0.\n * @dev Throws if the id does not point to a valid stream.\n * @param streamId The id of the stream for which to query the delta.\n * @notice Returns the time delta in seconds.\n */\n function deltaOf(uint256 streamId) public view streamExists(streamId) returns (uint256 delta) {\n Stream memory stream = _streams[streamId];\n if (block.timestamp <= stream.startTime) return 0;\n if (block.timestamp < stream.stopTime) return block.timestamp - stream.startTime;\n return stream.stopTime - stream.startTime;\n }\n\n struct BalanceOfLocalVars {\n uint256 recipientBalance;\n uint256 withdrawalAmount;\n uint256 senderBalance;\n }\n\n /// @inheritdoc ICollector\n function balanceOf(\n uint256 streamId,\n address who\n ) public view streamExists(streamId) returns (uint256 balance) {\n Stream memory stream = _streams[streamId];\n BalanceOfLocalVars memory vars;\n\n uint256 delta = deltaOf(streamId);\n vars.recipientBalance = delta * stream.ratePerSecond;\n\n /*\n * If the stream `balance` does not equal `deposit`, it means there have been withdrawals.\n * We have to subtract the total amount withdrawn from the amount of money that has been\n * streamed until now.\n */\n if (stream.deposit > stream.remainingBalance) {\n vars.withdrawalAmount = stream.deposit - stream.remainingBalance;\n vars.recipientBalance = vars.recipientBalance - vars.withdrawalAmount;\n }\n\n if (who == stream.recipient) return vars.recipientBalance;\n if (who == stream.sender) {\n vars.senderBalance = stream.remainingBalance - vars.recipientBalance;\n return vars.senderBalance;\n }\n return 0;\n }\n\n /*** Public Effects & Interactions Functions ***/\n\n /// @inheritdoc ICollector\n function approve(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmin {\n token.safeApprove(recipient, amount);\n }\n\n /// @inheritdoc ICollector\n function transfer(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmin {\n require(recipient != address(0), 'INVALID_0X_RECIPIENT');\n\n if (address(token) == ETH_MOCK_ADDRESS) {\n payable(recipient).sendValue(amount);\n } else {\n token.safeTransfer(recipient, amount);\n }\n }\n\n /// @dev needed in order to receive ETH from the Aave v1 ecosystem reserve\n receive() external payable {}\n\n /// @inheritdoc ICollector\n function setFundsAdmin(address admin) external onlyFundsAdmin {\n _setFundsAdmin(admin);\n }\n\n /**\n * @dev Transfer the ownership of the funds administrator role.\n * @param admin The address of the new funds administrator\n */\n function _setFundsAdmin(address admin) internal {\n _fundsAdmin = admin;\n emit NewFundsAdmin(admin);\n }\n\n struct CreateStreamLocalVars {\n uint256 duration;\n uint256 ratePerSecond;\n }\n\n /// @inheritdoc ICollector\n /**\n * @dev Throws if the recipient is the zero address, the contract itself or the caller.\n * Throws if the deposit is 0.\n * Throws if the start time is before `block.timestamp`.\n * Throws if the stop time is before the start time.\n * Throws if the duration calculation has a math error.\n * Throws if the deposit is smaller than the duration.\n * Throws if the deposit is not a multiple of the duration.\n * Throws if the rate calculation has a math error.\n * Throws if the next stream id calculation has a math error.\n * Throws if the contract is not allowed to transfer enough tokens.\n * Throws if there is a token transfer failure.\n */\n function createStream(\n address recipient,\n uint256 deposit,\n address tokenAddress,\n uint256 startTime,\n uint256 stopTime\n ) external onlyFundsAdmin returns (uint256) {\n require(recipient != address(0), 'stream to the zero address');\n require(recipient != address(this), 'stream to the contract itself');\n require(recipient != msg.sender, 'stream to the caller');\n require(deposit > 0, 'deposit is zero');\n require(startTime >= block.timestamp, 'start time before block.timestamp');\n require(stopTime > startTime, 'stop time before the start time');\n\n CreateStreamLocalVars memory vars;\n vars.duration = stopTime - startTime;\n\n /* Without this, the rate per second would be zero. */\n require(deposit >= vars.duration, 'deposit smaller than time delta');\n\n /* This condition avoids dealing with remainders */\n require(deposit % vars.duration == 0, 'deposit not multiple of time delta');\n\n vars.ratePerSecond = deposit / vars.duration;\n\n /* Create and store the stream object. */\n uint256 streamId = _nextStreamId;\n _streams[streamId] = Stream({\n remainingBalance: deposit,\n deposit: deposit,\n isEntity: true,\n ratePerSecond: vars.ratePerSecond,\n recipient: recipient,\n sender: address(this),\n startTime: startTime,\n stopTime: stopTime,\n tokenAddress: tokenAddress\n });\n\n /* Increment the next stream id. */\n _nextStreamId++;\n\n emit CreateStream(\n streamId,\n address(this),\n recipient,\n deposit,\n tokenAddress,\n startTime,\n stopTime\n );\n return streamId;\n }\n\n /// @inheritdoc ICollector\n /**\n * @dev Throws if the id does not point to a valid stream.\n * Throws if the caller is not the funds admin or the recipient of the stream.\n * Throws if the amount exceeds the available balance.\n * Throws if there is a token transfer failure.\n */\n function withdrawFromStream(\n uint256 streamId,\n uint256 amount\n ) external nonReentrant streamExists(streamId) onlyAdminOrRecipient(streamId) returns (bool) {\n require(amount > 0, 'amount is zero');\n Stream memory stream = _streams[streamId];\n\n uint256 balance = balanceOf(streamId, stream.recipient);\n require(balance >= amount, 'amount exceeds the available balance');\n\n _streams[streamId].remainingBalance = stream.remainingBalance - amount;\n\n if (_streams[streamId].remainingBalance == 0) delete _streams[streamId];\n\n IERC20(stream.tokenAddress).safeTransfer(stream.recipient, amount);\n emit WithdrawFromStream(streamId, stream.recipient, amount);\n return true;\n }\n\n /// @inheritdoc ICollector\n /**\n * @dev Throws if the id does not point to a valid stream.\n * Throws if the caller is not the funds admin or the recipient of the stream.\n * Throws if there is a token transfer failure.\n */\n function cancelStream(\n uint256 streamId\n ) external nonReentrant streamExists(streamId) onlyAdminOrRecipient(streamId) returns (bool) {\n Stream memory stream = _streams[streamId];\n uint256 senderBalance = balanceOf(streamId, stream.sender);\n uint256 recipientBalance = balanceOf(streamId, stream.recipient);\n\n delete _streams[streamId];\n\n IERC20 token = IERC20(stream.tokenAddress);\n if (recipientBalance > 0) token.safeTransfer(stream.recipient, recipientBalance);\n\n emit CancelStream(streamId, stream.sender, stream.recipient, senderBalance, recipientBalance);\n return true;\n }\n}\n",
"Address": "0x32709d47a008050e2a491b08bcbcf4b8f95bafa0"
}
],
"status": "1"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment