Contract 0xEa49D02c248b357B99670d9E9741F54f72dF9Cb3 11
Contract Overview
Balance:
0 ETH
ETH Value:
$0.00
My Name Tag:
Not Available
Txn Hash | Method |
Block
|
From
|
To
|
Value | [Txn Fee] | |||
---|---|---|---|---|---|---|---|---|---|
0x5fa8b0d4f2be8d294f5357823fe63c98eaacc96f19b12a0071d02adaef868dbf | 0x60806040 | 25637689 | 554 days 20 hrs ago | 0xb27446d86890f3f35d010b9adaeb135d2771a720 | IN | Create: PortalRegistryV1 | 0 ETH | 0.00020069 |
[ Download CSV Export ]
Latest 25 internal transaction
[ Download CSV Export ]
Contract Name:
PortalRegistryV1
Compiler Version
v0.8.11+commit.d7f03943
Contract Source Code (Solidity Standard Json-Input format)
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Aave V2 like markets using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/ILendingPool.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract AaveV2PortalIn is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Aave V2 like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be the underlying token) /// @param buyToken The Aave V2 like market address (i.e. the aToken) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param lendingPool The Aave V2 like market lending pool /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, ILendingPool lendingPool ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); uint256 intermediateAmount = _execute( sellToken, amount, intermediateToken, target, data ); buyAmount = _getBalance(msg.sender, buyToken); _approve(intermediateToken, address(lendingPool), intermediateAmount); lendingPool.deposit( intermediateToken, intermediateAmount, msg.sender, 0 ); buyAmount = _getBalance(msg.sender, buyToken) - buyAmount; if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice Base contract inherited by Portals /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "@openzeppelin/contracts/access/Ownable.sol"; import "../libraries/solmate/utils/SafeTransferLib.sol"; import "../interface/IWETH.sol"; import "../interface/IPortalFactory.sol"; import "../interface/IPortalRegistry.sol"; abstract contract PortalBaseV1_1 is Ownable { using SafeTransferLib for address; using SafeTransferLib for ERC20; // Active status of this contract. If false, contract is active (i.e un-paused) bool public paused; // Fee in basis points (bps) uint256 public fee; // Address of the Portal Registry IPortalRegistry public registry; // Address of the exchange used for swaps address public immutable exchange; // Address of the wrapped network token (e.g. WETH, wMATIC, wFTM, wAVAX, etc.) address public immutable wrappedNetworkToken; // Circuit breaker modifier pausable() { require(!paused, "Paused"); _; } constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry _registry, address _exchange, address _wrappedNetworkToken, uint256 _fee ) { wrappedNetworkToken = _wrappedNetworkToken; setFee(_fee); exchange = _exchange; registry = _registry; registry.addPortal(address(this), portalType, protocolId); transferOwnership(registry.owner()); } /// @notice Transfers tokens or the network token from the caller to this contract /// @param token The address of the token to transfer (address(0) if network token) /// @param quantity The quantity of tokens to transfer from the caller /// @dev quantity must == msg.value when token == address(0) /// @dev msg.value must == 0 when token != address(0) /// @return The quantity of tokens or network tokens transferred from the caller to this contract function _transferFromCaller(address token, uint256 quantity) internal virtual returns (uint256) { if (token == address(0)) { require( msg.value > 0 && msg.value == quantity, "Invalid quantity or msg.value" ); return msg.value; } require( quantity > 0 && msg.value == 0, "Invalid quantity or msg.value" ); ERC20(token).safeTransferFrom(msg.sender, address(this), quantity); return quantity; } /// @notice Returns the quantity of tokens or network tokens after accounting for the fee /// @param quantity The quantity of tokens to subtract the fee from /// @param feeBps The fee in basis points (BPS) /// @return The quantity of tokens or network tokens to transact with less the fee function _getFeeAmount(uint256 quantity, uint256 feeBps) internal view returns (uint256) { return registry.isPortal(msg.sender) ? quantity : quantity - (quantity * feeBps) / 10000; } /// @notice Executes swap or portal data at the target address /// @param sellToken The sell token /// @param sellAmount The quantity of sellToken (in sellToken base units) to send /// @param buyToken The buy token /// @param target The execution target for the data /// @param data The swap or portal data /// @return amountBought Quantity of buyToken acquired function _execute( address sellToken, uint256 sellAmount, address buyToken, address target, bytes memory data ) internal virtual returns (uint256 amountBought) { if (sellToken == buyToken) { return sellAmount; } if (sellToken == address(0) && buyToken == wrappedNetworkToken) { IWETH(wrappedNetworkToken).deposit{ value: sellAmount }(); return sellAmount; } if (sellToken == wrappedNetworkToken && buyToken == address(0)) { IWETH(wrappedNetworkToken).withdraw(sellAmount); return sellAmount; } uint256 valueToSend; if (sellToken == address(0)) { valueToSend = sellAmount; } else { _approve(sellToken, target, sellAmount); } uint256 initialBalance = _getBalance(address(this), buyToken); require( target == exchange || registry.isPortal(target), "Unauthorized target" ); (bool success, bytes memory returnData) = target.call{ value: valueToSend }(data); require(success, string(returnData)); amountBought = _getBalance(address(this), buyToken) - initialBalance; require(amountBought > 0, "Invalid execution"); } /// @notice Get the token or network token balance of an account /// @param account The owner of the tokens or network tokens whose balance is being queried /// @param token The address of the token (address(0) if network token) /// @return The owner's token or network token balance function _getBalance(address account, address token) internal view returns (uint256) { if (token == address(0)) { return account.balance; } else { return ERC20(token).balanceOf(account); } } /// @notice Approve a token for spending with finite allowance /// @param token The ERC20 token to approve /// @param spender The spender of the token /// @param amount The allowance to grant to the spender function _approve( address token, address spender, uint256 amount ) internal { ERC20 _token = ERC20(token); _token.safeApprove(spender, 0); _token.safeApprove(spender, amount); } /// @notice Collects tokens or network tokens from this contract /// @param tokens An array of the tokens to withdraw (address(0) if network token) function collect(address[] calldata tokens) external { address collector = registry.collector(); for (uint256 i = 0; i < tokens.length; i++) { uint256 qty; if (tokens[i] == address(0)) { qty = address(this).balance; collector.safeTransferETH(qty); } else { qty = ERC20(tokens[i]).balanceOf(address(this)); ERC20(tokens[i]).safeTransfer(collector, qty); } } } /// @dev Pause or unpause the contract function pause() external onlyOwner { paused = !paused; } /// @notice Sets the fee /// @param _fee The new fee amount between 0.06-1% function setFee(uint256 _fee) public onlyOwner { require(_fee >= 6 && _fee <= 100, "Invalid Fee"); fee = _fee; } /// @notice Updates the registry /// @param _registry The address of the new registry function updateRegistry(IPortalRegistry _registry) external onlyOwner { registry = _registry; } /// @notice Reverts if networks tokens are sent directly to this contract receive() external payable { require(msg.sender != tx.origin); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; enum PortalType { IN, OUT } interface IPortalRegistry { function addPortal( address portal, PortalType portalType, bytes32 protocolId ) external; function addPortalFactory( address portalFactory, PortalType portalType, bytes32 protocolId ) external; function removePortal(bytes32 protocolId, PortalType portalType) external; function owner() external view returns (address owner); function registrars(address origin) external view returns (bool isDeployer); function collector() external view returns (address collector); function isPortal(address portal) external view returns (bool isPortal); }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface ILendingPool { /** * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * - E.g. User deposits 100 USDC and gets in return 100 aUSDC * @param asset The address of the underlying asset to deposit * @param amount The amount to be deposited * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man **/ function deposit( address asset, uint256 amount, address onBehalfOf, uint16 referralCode ) external; /** * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC * @param asset The address of the underlying asset to withdraw * @param amount The underlying amount to be withdrawn * - Send the value type(uint256).max in order to withdraw the whole aToken balance * @param to Address that will receive the underlying, same as msg.sender if the user * wants to receive it on his own wallet, or a different address if the beneficiary is a * different wallet * @return The final amount withdrawn **/ function withdraw( address asset, uint256 amount, address to ) external returns (uint256); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides 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 {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import { ERC20 } from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or( and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()) ), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or( and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()) ), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore( freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000 ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or( and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()) ), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IWETH { function deposit() external payable; function withdraw(uint256 wad) external; }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "./IPortalRegistry.sol"; interface IPortalFactory { function fee() external view returns (uint256 fee); function registry() external view returns (IPortalRegistry registry); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval( address indexed owner, address indexed spender, uint256 amount ); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require( recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER" ); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Aave V2 like markets into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/ILendingPool.sol"; /// Thrown when insufficient buyAmount is received after withdrawal /// @param buyAmount The amount of tokens received /// @param minBuyAmount The minimum acceptable quantity of buyAmount error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract AaveV2PortalOut is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Remove liquidity from Aave V2 like pools into network tokens/ERC20 tokens /// @param sellToken The Aave V2 like market address (i.e. the aToken) /// @param sellAmount The quantity of sellToken to Portal out (aTokens are 1:1 with underlying asset) /// @param intermediateToken The underlying asset of the aToken /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param lendingPool The Aave V2 like market lending pool /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, ILendingPool lendingPool ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); uint256 intermediateAmount = lendingPool.withdraw( intermediateToken, amount, address(this) ); buyAmount = _execute( intermediateToken, intermediateAmount, buyToken, target, data ); buyAmount = _getFeeAmount(buyAmount, fee); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Uniswap V2-like pools into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "./interface/IUniswapV2Factory.sol"; import "./interface/IUniswapV2Router.sol"; import "./interface/IUniswapV2Pair.sol"; import "../interface/IPortalRegistry.sol"; contract UniswapV2PortalOut is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; uint256 internal constant DEADLINE = type(uint256).max; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); /// Thrown when insufficient liquidity is received after withdrawal /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Remove liquidity from Uniswap V2-like pools into network tokens/ERC20 tokens /// @param sellToken The pool (i.e. pair) address /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the swaps /// @param data The encoded calls for the buyToken swaps /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 minBuyAmount, address target, bytes[] calldata data, address partner, IUniswapV2Router02 router ) external pausable returns (uint256 buyAmount) { sellAmount = _transferFromCaller(sellToken, sellAmount); buyAmount = _remove( router, sellToken, sellAmount, buyToken, target, data ); buyAmount = _getFeeAmount(buyAmount, fee); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Removes both tokens from the pool and swaps for buyToken /// @param router The router belonging to the protocol to remove liquidity from /// @param sellToken The pair address (i.e. the LP address) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param sellAmount The quantity of LP tokens to remove from the pool /// @param target The excecution target for the swaps /// @param data The encoded calls for the buyToken swaps /// @return buyAmount The quantity of buyToken acquired function _remove( IUniswapV2Router02 router, address sellToken, uint256 sellAmount, address buyToken, address target, bytes[] calldata data ) internal returns (uint256 buyAmount) { IUniswapV2Pair pair = IUniswapV2Pair(sellToken); _approve(sellToken, address(router), sellAmount); address token0 = pair.token0(); address token1 = pair.token1(); (uint256 amount0, uint256 amount1) = router.removeLiquidity( token0, token1, sellAmount, 1, 1, address(this), DEADLINE ); buyAmount = _execute(token0, amount0, buyToken, target, data[0]); buyAmount += _execute(token1, amount1, buyToken, target, data[1]); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice Base contract inherited by Portal Factories /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "@openzeppelin/contracts/access/Ownable.sol"; import "../libraries/solmate/utils/SafeTransferLib.sol"; import "../interface/IWETH.sol"; import "../interface/IPortalFactory.sol"; import "../interface/IPortalRegistry.sol"; abstract contract PortalBaseV1 is Ownable { using SafeTransferLib for address; using SafeTransferLib for ERC20; // Active status of this contract. If false, contract is active (i.e un-paused) bool public paused; // Fee in basis points (bps) uint256 public fee; // Address of the Portal Registry IPortalRegistry public registry; // Address of the exchange used for swaps address public immutable exchange; // Address of the wrapped network token (e.g. WETH, wMATIC, wFTM, wAVAX, etc.) address public immutable wrappedNetworkToken; // Circuit breaker modifier pausable() { require(!paused, "Paused"); _; } constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry _registry, address _exchange, address _wrappedNetworkToken, uint256 _fee ) { wrappedNetworkToken = _wrappedNetworkToken; setFee(_fee); exchange = _exchange; registry = _registry; registry.addPortal(address(this), portalType, protocolId); transferOwnership(registry.owner()); } /// @notice Transfers tokens or the network token from the caller to this contract /// @param token The address of the token to transfer (address(0) if network token) /// @param quantity The quantity of tokens to transfer from the caller /// @dev quantity must == msg.value when token == address(0) /// @dev msg.value must == 0 when token != address(0) /// @return The quantity of tokens or network tokens transferred from the caller to this contract function _transferFromCaller(address token, uint256 quantity) internal virtual returns (uint256) { if (token == address(0)) { require( msg.value > 0 && msg.value == quantity, "Invalid quantity or msg.value" ); return msg.value; } require( quantity > 0 && msg.value == 0, "Invalid quantity or msg.value" ); ERC20(token).safeTransferFrom(msg.sender, address(this), quantity); return quantity; } /// @notice Returns the quantity of tokens or network tokens after accounting for the fee /// @param quantity The quantity of tokens to subtract the fee from /// @param feeBps The fee in basis points (BPS) /// @return The quantity of tokens or network tokens to transact with less the fee function _getFeeAmount(uint256 quantity, uint256 feeBps) internal pure returns (uint256) { return quantity - (quantity * feeBps) / 10000; } /// @notice Executes swap or portal data at the target address /// @param sellToken The sell token /// @param sellAmount The quantity of sellToken (in sellToken base units) to send /// @param buyToken The buy token /// @param target The execution target for the data /// @param data The swap or portal data /// @return amountBought Quantity of buyToken acquired function _execute( address sellToken, uint256 sellAmount, address buyToken, address target, bytes memory data ) internal virtual returns (uint256 amountBought) { if (sellToken == buyToken) { return sellAmount; } if (sellToken == address(0) && buyToken == wrappedNetworkToken) { IWETH(wrappedNetworkToken).deposit{ value: sellAmount }(); return sellAmount; } if (sellToken == wrappedNetworkToken && buyToken == address(0)) { IWETH(wrappedNetworkToken).withdraw(sellAmount); return sellAmount; } uint256 valueToSend; if (sellToken == address(0)) { valueToSend = sellAmount; } else { _approve(sellToken, target, sellAmount); } uint256 initialBalance = _getBalance(address(this), buyToken); require( target == exchange || registry.isPortal(target), "Unauthorized target" ); (bool success, bytes memory returnData) = target.call{ value: valueToSend }(data); require(success, string(returnData)); amountBought = _getBalance(address(this), buyToken) - initialBalance; require(amountBought > 0, "Invalid execution"); } /// @notice Get the token or network token balance of an account /// @param account The owner of the tokens or network tokens whose balance is being queried /// @param token The address of the token (address(0) if network token) /// @return The owner's token or network token balance function _getBalance(address account, address token) internal view returns (uint256) { if (token == address(0)) { return account.balance; } else { return ERC20(token).balanceOf(account); } } /// @notice Approve a token for spending with finite allowance /// @param token The ERC20 token to approve /// @param spender The spender of the token /// @param amount The allowance to grant to the spender function _approve( address token, address spender, uint256 amount ) internal { ERC20 _token = ERC20(token); _token.safeApprove(spender, 0); _token.safeApprove(spender, amount); } /// @notice Collects tokens or network tokens from this contract /// @param tokens An array of the tokens to withdraw (address(0) if network token) function collect(address[] calldata tokens) external { address collector = registry.collector(); for (uint256 i = 0; i < tokens.length; i++) { uint256 qty; if (tokens[i] == address(0)) { qty = address(this).balance; collector.safeTransferETH(qty); } else { qty = ERC20(tokens[i]).balanceOf(address(this)); ERC20(tokens[i]).safeTransfer(collector, qty); } } } /// @dev Pause or unpause the contract function pause() external onlyOwner { paused = !paused; } /// @notice Sets the fee /// @param _fee The new fee amount between 0.06-1% function setFee(uint256 _fee) public onlyOwner { require(_fee >= 6 && _fee <= 100, "Invalid Fee"); fee = _fee; } /// @notice Updates the registry /// @param _registry The address of the new registry function updateRegistry(IPortalRegistry _registry) external onlyOwner { registry = _registry; } /// @notice Reverts if networks tokens are sent directly to this contract receive() external payable { require(msg.sender != tx.origin); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; interface IUniswapV2Factory { function getPair(address tokenA, address tokenB) external view returns (address); }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; interface IUniswapV2Router02 { function addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external returns ( uint256 amountA, uint256 amountB, uint256 liquidity ); function removeLiquidity( address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external returns (uint256 amountA, uint256 amountB); function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory amounts); function factory() external pure returns (address); }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; interface IUniswapV2Pair { function token0() external pure returns (address); function token1() external pure returns (address); function getReserves() external view returns ( uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast ); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Uniswap V2-like pools using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "./interface/Babylonian.sol"; import "./interface/IUniswapV2Factory.sol"; import "./interface/IUniswapV2Router.sol"; import "./interface/IUniswapV2Pair.sol"; import "../interface/IPortalRegistry.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract UniswapV2PortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Uniswap V2-like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be one of the pool tokens) /// @param buyToken The pool (i.e. pair) address /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param router The Uniswap V2-like router to be used for adding liquidity /// @param returnResidual Return residual, if any, that remains /// following the deposit. Note: Probably a waste of gas to set to true /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, IUniswapV2Router02 router, bool returnResidual ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); buyAmount = _deposit( router, intermediateToken, amount, buyToken, returnResidual ); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Sets up the correct token ratio and deposits into the pool /// @param router The router belonging to the protocol to add liquidity to /// @param sellToken The token address to swap from /// @param sellAmount The quantity of tokens to sell /// @param buyToken The pool (i.e. pair) address /// @param returnResidual Return residual, if any, that remains /// following the deposit. Note: Probably a waste of gas to set to true /// @return liquidity The quantity of LP tokens acquired function _deposit( IUniswapV2Router02 router, address sellToken, uint256 sellAmount, address buyToken, bool returnResidual ) internal returns (uint256 liquidity) { IUniswapV2Pair pair = IUniswapV2Pair(buyToken); (uint256 res0, uint256 res1, ) = pair.getReserves(); address token0 = pair.token0(); address token1 = pair.token1(); uint256 token0Amount; uint256 token1Amount; if (sellToken == token0) { uint256 swapAmount = _getSwapAmount(res0, sellAmount); if (swapAmount <= 0) swapAmount = sellAmount / 2; token1Amount = _intraSwap( router, sellToken, swapAmount, pair.token1() ); token0Amount = sellAmount - swapAmount; } else { uint256 swapAmount = _getSwapAmount(res1, sellAmount); if (swapAmount <= 0) swapAmount = sellAmount / 2; token0Amount = _intraSwap(router, sellToken, swapAmount, token0); token1Amount = sellAmount - swapAmount; } liquidity = _addLiquidity( router, buyToken, token0, token0Amount, token1, token1Amount, returnResidual ); } /// @notice Returns the optimal intra-pool swap quantity such that /// that the proportion of both tokens held subsequent to the swap is /// equal to the proportion of the assets in the pool. Assumes typical /// Uniswap V2 fee. /// @param reserves The reserves of the sellToken /// @param amount The total quantity of tokens held /// @return The quantity of the sell token to swap function _getSwapAmount(uint256 reserves, uint256 amount) internal pure returns (uint256) { return (Babylonian.sqrt( reserves * ((amount * 3988000) + (reserves * 3988009)) ) - (reserves * 1997)) / 1994; } /// @notice Used for intra-pool swaps of ERC20 assets /// @param router The Uniswap V2-like router to use for the swap /// @param sellToken The token address to swap from /// @param sellAmount The quantity of tokens to sell /// @param buyToken The token address to swap to /// @return tokenBought The quantity of tokens bought function _intraSwap( IUniswapV2Router02 router, address sellToken, uint256 sellAmount, address buyToken ) internal returns (uint256 tokenBought) { if (sellToken == buyToken) { return sellAmount; } _approve(sellToken, address(router), sellAmount); address[] memory path = new address[](2); path[0] = sellToken; path[1] = buyToken; tokenBought = router.swapExactTokensForTokens( sellAmount, 1, path, address(this), block.timestamp )[path.length - 1]; } /// @notice Deposits both tokens into the pool /// @param router The Uniswap V2-like router to use to add liquidity /// @param token0Amount The quantity of token0 to add to the pool /// @param token1 The address of the 1st token in the pool /// @param token1Amount The quantity of token1 to add to the pool /// @param returnResidual Return residual, if any, that remains /// following the deposit. Note: Probably a waste of gas to set to true /// @return liquidity pool tokens acquired function _addLiquidity( IUniswapV2Router02 router, address buyToken, address token0, uint256 token0Amount, address token1, uint256 token1Amount, bool returnResidual ) internal returns (uint256) { _approve(token0, address(router), token0Amount); _approve(token1, address(router), token1Amount); uint256 beforeLiquidity = _getBalance(msg.sender, buyToken); (uint256 amountA, uint256 amountB, ) = router.addLiquidity( token0, token1, token0Amount, token1Amount, 1, 1, msg.sender, block.timestamp ); if (returnResidual) { if (token0Amount - amountA > 0) { ERC20(token0).safeTransfer(msg.sender, token0Amount - amountA); } if (token1Amount - amountB > 0) { ERC20(token1).safeTransfer(msg.sender, token1Amount - amountB); } } return _getBalance(msg.sender, buyToken) - beforeLiquidity; } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; library Babylonian { function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Yearn Vaults using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "./interface/IVault.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract YearnPortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Yearn like vaults with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be the vault underlying token) /// @param buyToken The vault token address (i.e. the vault token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); uint256 balance = _getBalance(address(this), buyToken); _approve(intermediateToken, buyToken, amount); uint256 valueToSend = intermediateToken == address(0) ? amount : 0; IVault(buyToken).deposit{ value: valueToSend }(amount); buyAmount = _getBalance(address(this), buyToken) - balance; if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IVault { function deposit(uint256 _amount) external payable; function withdraw(uint256 _amount) external; function permit( address owner, address spender, uint256 amount, uint256 expiry, bytes calldata signature ) external returns (bool); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Yearn Vaults into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "./interface/IVault.sol"; /// Thrown when insufficient liquidity is received after withdrawal /// @param buyAmount The amount of tokens received /// @param minBuyAmount The minimum acceptable quantity of buyAmount error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract YearnPortalOut is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Remove liquidity from Yearn like vaults into network tokens/ERC20 tokens /// @param sellToken The vault token address /// @param sellAmount The quantity of sellToken to Portal out /// @param intermediateToken The intermediate token to swap from (must be the vault underlying token) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner ) public payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); uint256 balance = _getBalance(address(this), intermediateToken); IVault(sellToken).withdraw(amount); amount = _getBalance(address(this), intermediateToken) - balance; buyAmount = _execute(intermediateToken, amount, buyToken, target, data); buyAmount = _getFeeAmount(buyAmount, fee); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Remove liquidity from Yearn like vaults into network tokens/ERC20 tokens with permit /// @param sellToken The vault token address /// @param sellAmount The quantity of sellToken to Portal out /// @param intermediateToken The intermediate token to swap from (must be the vault underlying token) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param signature A valid secp256k1 signature of Permit by owner encoded as r, s, v /// @return buyAmount The quantity of buyToken acquired function portalOutWithPermit( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, bytes calldata signature ) external payable pausable returns (uint256 buyAmount) { _permit(sellToken, sellAmount, signature); return portalOut( sellToken, sellAmount, intermediateToken, buyToken, minBuyAmount, target, data, partner ); } function _permit( address sellToken, uint256 sellAmount, bytes calldata signature ) internal { bool success = IVault(sellToken).permit( msg.sender, address(this), sellAmount, 0, signature ); require(success, "Could Not Permit"); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Yearn Vaults using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "./interface/IYearnPartnerTracker.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract YearnPartnerPortalIn is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IYearnPartnerTracker immutable YearnPartnerTracker; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IYearnPartnerTracker yearnPartnerTracker ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { YearnPartnerTracker = yearnPartnerTracker; } /// @notice Add liquidity to Yearn like vaults with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be the vault underlying token) /// @param buyToken The vault token address (i.e. the vault token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param yearnAffiliate The Yearn affiliate address /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, address yearnAffiliate ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); uint256 balance = _getBalance(address(this), buyToken); _approve(intermediateToken, address(YearnPartnerTracker), amount); YearnPartnerTracker.deposit(buyToken, yearnAffiliate, amount); buyAmount = _getBalance(address(this), buyToken) - balance; if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IYearnPartnerTracker { /** * @notice Deposit into a vault the specified amount from depositer * @param vault The address of the vault * @param partnerId The address of the partner who has referred this deposit * @param amount The amount to deposit * @return The number of yVault tokens received */ function deposit( address vault, address partnerId, uint256 amount ) external returns (uint256); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Stargate pools into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IStargateRouter.sol"; /// Thrown when insufficient liquidity is received after withdrawal /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract StargatePortalOut is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IStargateRouter public immutable ROUTER; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IStargateRouter _router ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { ROUTER = _router; } /// @notice Remove liquidity from Stargate pools into network tokens/ERC20 tokens /// @param sellToken The Stargate pool address (i.e. the LP token address) /// @param sellAmount The quantity of sellToken to Portal out /// @param intermediateToken The intermediate token to swap from (must be an underlying pool token) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param poolId The ID of the pool /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, uint16 poolId ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); _approve(sellToken, address(ROUTER), amount); uint256 intermediateAmount = _getBalance( address(this), intermediateToken ); ROUTER.instantRedeemLocal(poolId, amount, address(this)); intermediateAmount = _getBalance(address(this), intermediateToken) - intermediateAmount; buyAmount = _execute( intermediateToken, intermediateAmount, buyToken, target, data ); buyAmount = _getFeeAmount(buyAmount, fee); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IStargateRouter { function addLiquidity( uint256 _poolId, uint256 _amountLD, address _to ) external; function instantRedeemLocal( uint16 _srcPoolId, uint256 _amountLP, address _to ) external returns (uint256 amountSD); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Stargate like pools using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IStargateRouter.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract StargatePortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IStargateRouter public immutable ROUTER; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IStargateRouter _router ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { ROUTER = _router; } /// @notice Add liquidity to Stargate like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be one of the pool tokens) /// @param buyToken The Stargate pool address (i.e. the LP token address) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param poolId The ID of the pool /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, uint256 poolId ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); uint256 balance = _getBalance(msg.sender, buyToken); _approve(intermediateToken, address(ROUTER), amount); ROUTER.addLiquidity(poolId, amount, msg.sender); buyAmount = _getBalance(msg.sender, buyToken) - balance; if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Curve pools into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "./interface/ICurveAddressProvider.sol"; import "./interface/ICurvePool.sol"; import "./interface/ICurveRegistry.sol"; /// Thrown when insufficient liquidity is received after withdrawal /// @param buyAmount The amount of tokens received /// @param minBuyAmount The minimum acceptable quantity of buyAmount error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract CurvePortalOut is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Removes liquidity from Curve pools into network tokens/ERC20 tokens /// @param sellToken The curve pool token address /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param poolData Encoded pool data including the following for the pool and metapool sequentially (if applicable): /// pool The address of the swap/deposit contract (address(0) for metapool set if not metapool) /// intermediateToken The address of the token at the index /// The index of the token being removed from the pool (i.e the index of intermediateToken) /// isInt128 A boolean value specifying whether the index is int128 or uint256 /// removeUnderlying A boolean value specifying whether to withdraw the unwrapped version of the token /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, bytes calldata poolData ) external payable pausable returns (uint256 buyAmount) { sellAmount = _transferFromCaller(sellToken, sellAmount); address intermediateToken; (sellAmount, intermediateToken) = _remove( sellToken, sellAmount, poolData ); buyAmount = _execute( intermediateToken, sellAmount, buyToken, target, data ); buyAmount = _getFeeAmount(buyAmount, fee); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Handles removal of tokens and parsing of pooldata for pools and metapools /// @param sellToken The curve pool token address /// @param sellAmount The quantity of sellToken to Portal out /// @param poolData Encoded pool data including the following for the pool and metapool sequentially (if applicable): /// pool The address of the swap/deposit contract (address(0) for metapool set if not metapool) /// intermediateToken The address of the token at the index /// The index of the token being removed from the pool (i.e the index of intermediateToken) /// isInt128 A boolean value specifying whether the index is int128 or uint256 /// removeUnderlying A boolean value specifying whether to withdraw the unwrapped version of the token /// @return buyAmount The quantity buyToken acquired /// @return intermediateToken The address of the intermediate token to swap to buyToken function _remove( address sellToken, uint256 sellAmount, bytes calldata poolData ) internal returns (uint256 buyAmount, address intermediateToken) { address pool; uint256 coinIndex; bool isInt128; bool removeUnderlying; ( pool, intermediateToken, coinIndex, isInt128, removeUnderlying ) = _parsePoolData(poolData, false); buyAmount = _exitCurve( sellToken, sellAmount, pool, intermediateToken, coinIndex, isInt128, removeUnderlying ); if ( keccak256(abi.encodePacked(poolData[160:180])) != keccak256(abi.encodePacked(address(0))) ) { address poolToken = intermediateToken; ( pool, intermediateToken, coinIndex, isInt128, removeUnderlying ) = _parsePoolData(poolData, true); buyAmount = _exitCurve( poolToken, buyAmount, pool, intermediateToken, coinIndex, isInt128, removeUnderlying ); } } /// @notice Removes liquidity from the pool /// @param sellToken The curve pool token address /// @param sellAmount The quantity of sellToken to Portal out /// @param pool The address of the swap/deposit contract (address(0) for metapool set if not metapool) /// @param coinIndex The index of the token being removed from the pool (i.e the index of intermediateToken) /// @param isInt128 A boolean value specifying whether the index is int128 or uint256 /// @param removeUnderlying A boolean value specifying whether to withdraw the unwrapped version of the token /// @return buyAmount The quantity buyToken acquired function _exitCurve( address sellToken, uint256 sellAmount, address pool, address buyToken, uint256 coinIndex, bool isInt128, bool removeUnderlying ) internal returns (uint256) { _approve(sellToken, pool, sellAmount); uint256 balance = _getBalance(address(this), buyToken); ICurvePool _pool = ICurvePool(pool); if (isInt128) { if (removeUnderlying) { _pool.remove_liquidity_one_coin( sellAmount, int128(uint128(coinIndex)), 0, true ); } else { _pool.remove_liquidity_one_coin( sellAmount, int128(uint128(coinIndex)), 0 ); } } else { if (removeUnderlying) { _pool.remove_liquidity_one_coin(sellAmount, coinIndex, 0, true); } else { _pool.remove_liquidity_one_coin(sellAmount, coinIndex, 0); } } return _getBalance(address(this), buyToken) - balance; } function _parsePoolData(bytes calldata poolData, bool isMetapool) internal pure returns ( address pool, address intermediateToken, uint256 coinIndex, bool isInt128, bool removeUnderlying ) { if (isMetapool) { ( , , , , , pool, intermediateToken, coinIndex, isInt128, removeUnderlying ) = abi.decode( poolData, ( address, address, uint256, bool, bool, address, address, uint256, bool, bool ) ); } else { ( pool, intermediateToken, coinIndex, isInt128, removeUnderlying, , , , , ) = abi.decode( poolData, ( address, address, uint256, bool, bool, address, address, uint256, bool, bool ) ); } } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.11; interface ICurveAddressProvider { function get_registry() external view returns (address); function get_address(uint256 _id) external view returns (address); }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.11; interface ICurvePool { function add_liquidity(uint256[2] memory _amounts, uint256 _min_mint_amount) external payable; function add_liquidity( uint256[2] memory _amounts, uint256 _min_mint_amount, bool _use_underlying ) external payable; function add_liquidity(uint256[3] memory _amounts, uint256 _min_mint_amount) external payable; function add_liquidity( uint256[3] memory _amounts, uint256 _min_mint_amount, bool _use_underlying ) external payable; function add_liquidity(uint256[4] memory _amounts, uint256 _min_mint_amount) external payable; function add_liquidity( uint256[4] memory _amounts, uint256 _min_mint_amount, bool _use_underlying ) external payable; function coins(uint256 i) external view returns (address); function remove_liquidity_one_coin( uint256 _token_amount, int128 i, uint256 min_amount ) external; function remove_liquidity_one_coin( uint256 token_amount, uint256 i, uint256 min_amount ) external; function remove_liquidity_one_coin( uint256 token_amount, uint256 i, uint256 min_amount, bool _use_underlying ) external; function remove_liquidity_one_coin( uint256 token_amount, int128 i, uint256 min_amount, bool _use_underlying ) external; }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.11; interface ICurveRegistry { function get_coins(address _pool) external view returns (address[8] memory); function get_n_coins(address _pool) external view returns (uint256); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Curve pools using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "./interface/ICurveAddressProvider.sol"; import "./interface/ICurvePool.sol"; import "./interface/ICurveRegistry.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract CurvePortalIn is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Curve pools with network tokens/ERC20 tokens /// @dev This contract can call itself in cases where the pool is a metapool. /// In these cases, transfers, events and fees are omitted. /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be one of the pool tokens) /// @param buyToken The curve pool token address /// NOTE This may be different from the swap/deposit address! /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param poolData Encoded pool data including the following: /// pool The address of the swap/deposit contract /// numCoins The number of coins in the pool /// The index of the intermediateToken in the pool /// depositUnderlying A boolean value specifying whether to deposit the unwrapped version of intermediateToken /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, bytes calldata poolData ) external payable pausable returns (uint256 buyAmount) { uint256 amount = sellAmount; if (msg.sender != address(this)) { amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); } amount = _execute(sellToken, amount, intermediateToken, target, data); buyAmount = _deposit(intermediateToken, amount, buyToken, poolData); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); if (msg.sender != address(this)) { ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } } /// @notice Deposits the sellToken into the pool using the correct interface based on the /// number of coins in the pool /// @param sellToken The token address to swap from /// @param sellAmount The quantity of tokens to sell /// @param buyToken The curve pool token address /// @param poolData Encoded pool data including the following: /// pool The address of the swap/deposit contract /// numCoins The number of coins in the pool /// The index of the intermediateToken in the pool /// depositUnderlying A boolean value specifying whether to deposit the unwrapped version of intermediateToken /// @return liquidity The quantity of LP tokens acquired function _deposit( address sellToken, uint256 sellAmount, address buyToken, bytes calldata poolData ) internal returns (uint256) { ( address pool, uint256 numCoins, uint256 coinIndex, bool depositUnderlying ) = abi.decode(poolData, (address, uint256, uint256, bool)); uint256 valueToSend; if (sellToken == address(0)) { valueToSend = sellAmount; } else { _approve(sellToken, pool, sellAmount); } uint256 balance = _getBalance(address(this), buyToken); ICurvePool _pool = ICurvePool(pool); if (numCoins == 2) { uint256[2] memory _amounts; _amounts[coinIndex] = sellAmount; depositUnderlying ? _pool.add_liquidity{ value: valueToSend }(_amounts, 0, true) : _pool.add_liquidity{ value: valueToSend }(_amounts, 0); } else if (numCoins == 3) { uint256[3] memory _amounts; _amounts[coinIndex] = sellAmount; depositUnderlying ? _pool.add_liquidity{ value: valueToSend }(_amounts, 0, true) : _pool.add_liquidity{ value: valueToSend }(_amounts, 0); } else { uint256[4] memory _amounts; _amounts[coinIndex] = sellAmount; depositUnderlying ? _pool.add_liquidity{ value: valueToSend }(_amounts, 0, true) : _pool.add_liquidity{ value: valueToSend }(_amounts, 0); } return _getBalance(address(this), buyToken) - balance; } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract deposits into Aave V3 using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1_1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IPool.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract AaveV3PortalIn is PortalBaseV1_1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1_1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Aave V3 like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be the underlying token) /// @param buyToken The Aave V3 like market address (i.e. the aToken) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param lendingPool The Aave V3 like market lending pool /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, IPool lendingPool ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); uint256 intermediateAmount = _execute( sellToken, amount, intermediateToken, target, data ); buyAmount = _getBalance(msg.sender, buyToken); _approve(intermediateToken, address(lendingPool), intermediateAmount); lendingPool.supply( intermediateToken, intermediateAmount, msg.sender, 0 ); buyAmount = _getBalance(msg.sender, buyToken) - buyAmount; if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IPool { /** * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * - E.g. User supplies 100 USDC and gets in return 100 aUSDC * @param asset The address of the underlying asset to supply * @param amount The amount to be supplied * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man **/ function supply( address asset, uint256 amount, address onBehalfOf, uint16 referralCode ) external; /** * @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC * @param asset The address of the underlying asset to withdraw * @param amount The underlying amount to be withdrawn * - Send the value type(uint256).max in order to withdraw the whole aToken balance * @param to The address that will receive the underlying, same as msg.sender if the user * wants to receive it on his own wallet, or a different address if the beneficiary is a * different wallet * @return The final amount withdrawn **/ function withdraw( address asset, uint256 amount, address to ) external returns (uint256); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice Base contract inherited by Portal Factories /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "@openzeppelin/contracts/access/Ownable.sol"; import "../libraries/solmate/utils/SafeTransferLib.sol"; import "../interface/IWETH.sol"; import "../interface/IPortalFactory.sol"; import "../interface/IPortalRegistry.sol"; abstract contract PortalFactoryBaseV1 is Ownable { using SafeTransferLib for address; using SafeTransferLib for ERC20; // Active status of this contract. If false, contract is active (i.e un-paused) bool public paused; // Fee in basis points (bps) uint256 public fee; // Address of the Portal Factory IPortalFactory public immutable factory; // Address of the Portal Registry IPortalRegistry public registry; // Address of the exchange used for swaps address public immutable exchange; // Address of the wrapped network token (e.g. WETH, wMATIC, wFTM, wAVAX, etc.) address public immutable wrappedNetworkToken; // Circuit breaker modifier pausable() { require(!paused, "Paused"); _; } constructor( bytes32 protocolId, PortalType portalType, address _factory, IPortalRegistry _registry, address _exchange, address _wrappedNetworkToken, uint256 _fee ) { factory = IPortalFactory(_factory); wrappedNetworkToken = _wrappedNetworkToken; fee = _fee; exchange = _exchange; registry = _registry; registry.addPortal(address(this), portalType, protocolId); transferOwnership(registry.owner()); } /// @notice Transfers tokens or the network token from the caller to this contract /// @param token The address of the token to transfer (address(0) if network token) /// @param quantity The quantity of tokens to transfer from the caller (ignored if network tokens) /// @return The quantity of tokens or network tokens transferred from the caller to this contract function _transferFromCaller(address token, uint256 quantity) internal virtual returns (uint256) { if (token == address(0)) { require(msg.value > 0, "No network tokens sent"); return msg.value; } require(quantity > 0, "Invalid quantity"); require(msg.value == 0, "Network tokens sent with token"); ERC20(token).safeTransferFrom(msg.sender, address(this), quantity); return quantity; } /// @notice Returns the quantity of tokens or network tokens after accounting for the fee /// @param quantity The quantity of tokens being transacted /// @param feeBps The fee in basis points (BPS) /// @return The quantity of tokens or network tokens to transact with less the fee function _getFeeAmount(uint256 quantity, uint256 feeBps) internal pure returns (uint256) { return quantity - (quantity * feeBps) / 10000; } /// @notice Executes swap or portal data at the target address /// @param sellToken The sell token /// @param sellAmount The quantity of sellToken (in sellToken base units) to send /// @param buyToken The buy token /// @param target The execution target for the data /// @param data The swap or portal data /// @return amountBought Quantity of buyToken acquired function _execute( address sellToken, uint256 sellAmount, address buyToken, address target, bytes memory data ) internal virtual returns (uint256 amountBought) { if (sellToken == buyToken) { return sellAmount; } if (sellToken == address(0) && buyToken == wrappedNetworkToken) { IWETH(wrappedNetworkToken).deposit{ value: sellAmount }(); return sellAmount; } if (sellToken == wrappedNetworkToken && buyToken == address(0)) { IWETH(wrappedNetworkToken).withdraw(sellAmount); return sellAmount; } uint256 valueToSend; if (sellToken == address(0)) { valueToSend = sellAmount; } else { _approve(sellToken, target, sellAmount); } uint256 initialBalance = _getBalance(address(this), buyToken); require( target == exchange || registry.isPortal(target), "Unauthorized target" ); (bool success, bytes memory returnData) = target.call{ value: valueToSend }(data); require(success, string(returnData)); amountBought = _getBalance(address(this), buyToken) - initialBalance; require(amountBought > 0, "Invalid execution"); } /// @notice Get the token or network token balance of an account /// @param account The owner of the tokens or network tokens whose balance is being queried /// @param token The address of the token (address(0) if network token) /// @return The owner's token or network token balance function _getBalance(address account, address token) internal view returns (uint256) { if (token == address(0)) { return account.balance; } else { return ERC20(token).balanceOf(account); } } /// @notice Approve a token for spending with finite allowance /// @param token The ERC20 token to approve /// @param spender The spender of the token /// @param amount The allowance to grant to the spender function _approve( address token, address spender, uint256 amount ) internal { ERC20 _token = ERC20(token); _token.safeApprove(spender, 0); _token.safeApprove(spender, amount); } /// @notice Collects tokens or network tokens from this contract /// @param tokens An array of the tokens to withdraw (address(0) if network token) function collect(address[] calldata tokens) external { address collector = registry.collector(); for (uint256 i = 0; i < tokens.length; i++) { uint256 qty; if (tokens[i] == address(0)) { qty = address(this).balance; collector.safeTransferETH(qty); } else { qty = ERC20(tokens[i]).balanceOf(address(this)); ERC20(tokens[i]).safeTransfer(collector, qty); } } } /// @dev Pause or unpause the contract function pause() external onlyOwner { paused = !paused; } /// @notice Updates the fee from the factory function updateFee() external { fee = factory.fee(); } /// @notice Updates the registry from the factory function updateRegistry() external { registry = factory.registry(); } /// @notice Reverts if networks tokens are sent directly to this contract receive() external payable { require(msg.sender != tx.origin); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "./ERC20.sol"; import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; /// @notice Minimalist and modern Wrapped Ether implementation. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/WETH.sol) /// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) contract WETH is ERC20("Wrapped Ether", "WETH", 18) { using SafeTransferLib for address; event Deposit(address indexed from, uint256 amount); event Withdrawal(address indexed to, uint256 amount); function deposit() public payable virtual { _mint(msg.sender, msg.value); emit Deposit(msg.sender, msg.value); } function withdraw(uint256 amount) public virtual { _burn(msg.sender, amount); emit Withdrawal(msg.sender, amount); msg.sender.safeTransferETH(amount); } receive() external payable virtual { deposit(); } }
// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice Registry of Portals and Portal Factories /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "@openzeppelin/contracts/access/Ownable.sol"; contract PortalRegistryV1 is Ownable { // The multisig collector address address public collector; // The addresses of the registrars mapping(address => bool) public registrars; // Tracks existing portals for use as targets for calldata execution mapping(address => bool) public isPortal; // Tracks portal partners for revenue sharing mapping(address => bool) public partners; // Returns a portal address given a protocolId and portal type mapping(bytes32 => mapping(PortalType => Portal)) public getPortalById; // Tracks supported platforms bytes32[] internal supportedPlatforms; // Tracks the total number of portals uint256 public numPortals; // The type of Portal where 1 = Portal In and 2 = Portal Out enum PortalType { IN, OUT } struct Portal { address portal; PortalType portalType; bytes32 protocolId; uint96 version; bool active; } /// @notice Emitted when a new portal is created /// @param portal The newly created portal /// @param numPortals The total number of portals in existence event AddPortal(Portal portal, uint256 numPortals); /// @notice Emitted when a portal is updated /// @param portal The updated portal /// @param numPortals The total number of portals in existence event UpdatePortal(Portal portal, uint256 numPortals); /// @notice Emitted when a portal is removed /// @param portal The removed portal /// @param numPortals The total number of portals in existence event RemovePortal(Portal portal, uint256 numPortals); // Only registrars may add new portals to the registry modifier onlyRegistrars() { require(registrars[tx.origin], "Invalid origin"); _; } constructor(address _collector, address _owner) { collector = _collector; registrars[msg.sender] = true; registrars[_owner] = true; transferOwnership(_owner); } /// @notice Adds new portals deployed by active registrars /// @param portal The address of the new portal /// @param portalType The type of portal - in or out /// @param protocolId The bytes32 representation of the name of the protocol function addPortal( address portal, PortalType portalType, bytes32 protocolId ) external onlyRegistrars { Portal storage existingPortal = getPortalById[protocolId][portalType]; if (existingPortal.version != 0) { isPortal[existingPortal.portal] = false; existingPortal.portal = portal; existingPortal.version++; existingPortal.active = true; isPortal[portal] = true; emit UpdatePortal(existingPortal, numPortals); } else { Portal memory newPortal = Portal( portal, portalType, protocolId, 1, true ); getPortalById[protocolId][portalType] = newPortal; isPortal[portal] = true; supportedPlatforms.push(protocolId); emit AddPortal(newPortal, numPortals++); } } /// @notice Removes an inactivates existing portals /// @param portalType The type of portal - in or out /// @param protocolId The bytes32 representation of the name of the protocol function removePortal(bytes32 protocolId, PortalType portalType) external onlyOwner { Portal storage deletedPortal = getPortalById[protocolId][portalType]; deletedPortal.active = false; isPortal[deletedPortal.portal] = false; emit RemovePortal(deletedPortal, numPortals); } /// @notice Returns an array of all of the portal objects by type /// @param portalType The type of portal - in or out function getAllPortals(PortalType portalType) external view returns (Portal[] memory) { Portal[] memory portals = new Portal[](numPortals); for (uint256 i = 0; i < supportedPlatforms.length; i++) { Portal memory portal = getPortalById[supportedPlatforms[i]][ portalType ]; portals[i] = portal; } return portals; } /// @notice Returns an array of all supported platforms function getSupportedPlatforms() external view returns (bytes32[] memory) { return supportedPlatforms; } /// @notice Updates a registrar's active status /// @param registrar The address of the registrar /// @param active The status of the registrar. Set true if /// the registrar is active, false otherwise function updateRegistrars(address registrar, bool active) external onlyOwner { registrars[registrar] = active; } /// @notice Updates a partner's active status /// @param partner The address of the registrar /// @param active The status of the partner. Set true if /// the partner is active, false otherwise function updatePartners(address partner, bool active) external onlyOwner { partners[partner] = active; } /// @notice Updates the collector's address /// @param _collector The address of the new collector function updateCollector(address _collector) external onlyOwner { collector = _collector; } /// @notice Helper function to convert a protocolId string into bytes32 function stringToBytes32(string memory _string) external pure returns (bytes32 _bytes32String) { assembly { _bytes32String := mload(add(_string, 32)) } } /// @notice Helper function to convert protocolId bytes32 into a string function bytes32ToString(bytes32 _bytes) external pure returns (string memory) { return string(abi.encode(_bytes)); } }
// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice Portals registry address provider /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "@openzeppelin/contracts/access/Ownable.sol"; contract PortalAddressProviderV1 is Ownable { /// @notice Registry is inactive if the registry address is address(0) struct RegistryInfo { address addr; uint256 version; uint256 updated; string description; } // Array of registries RegistryInfo[] internal registries; /// @notice Emitted when a new registry is added /// @param registry The newly added registry event AddRegistry(RegistryInfo registry); /// @notice Emitted when a registry is removed /// @param registry The removed registry event RemoveRegistry(RegistryInfo registry); /// @notice Emitted when a registry is updated /// @param registry The updated registry event UpdateRegistry(RegistryInfo registry); constructor(address _owner) { transferOwnership(_owner); } /// @notice Adds a new registry /// @param addr The address of the new registry /// @param description The description of the registry function addRegistry(address addr, string memory description) external onlyOwner { uint256 index = registries.length; registries.push(RegistryInfo(addr, 1, block.timestamp, description)); emit AddRegistry(registries[index]); } /// @notice Removes a registry /// @dev sets the registry (address) to address(0) /// @param index The index of the registry in the registries array that is being removed function removeRegistry(uint256 index) external onlyOwner { registries[index].addr = address(0); registries[index].updated = block.timestamp; emit RemoveRegistry(registries[index]); } /// @notice Updates a registry /// @param index The index of the registry in the registries array that is being updated /// @param addr The address of the updated registry function updateRegistry(uint256 index, address addr) external onlyOwner { registries[index].addr = addr; ++registries[index].version; registries[index].updated = block.timestamp; emit UpdateRegistry(registries[index]); } /// @notice Returns an array of all of the registry info objects function getAllRegistries() external view returns (RegistryInfo[] memory) { return registries; } /// @notice Returns the address of the main registry function getRegistry() external view returns (address) { return registries[0].addr; } /// @notice Returns the address of the registry at the index /// @param index The index of the registry in the registries array whose address is being returned function getAddress(uint256 index) external view returns (address) { return registries[index].addr; } /// @notice Returns the total number of registries function numRegistries() external view returns (uint256) { return registries.length; } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity and stakes liquiditity into Convex like pools using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IBaseRewardPool.sol"; import "./interface/IBooster.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract ConvexPortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IBooster internal immutable BOOSTER; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IBooster _booster ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { BOOSTER = _booster; } /// @notice Add liquidity and stake into Convex like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be the underlying LP token) /// @param buyToken The Convex like deposit token address (i.e. the cvxToken) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// note: crv tokens are 1:1 with cvx tokens /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @param rewardPool The base reward pool for the buyToken /// @return buyAmount The quantity of buyToken acquired /// @dev buyAmount is staked and not returned to msg.sender! function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, IBaseRewardPool rewardPool ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); buyAmount = _deposit(intermediateToken, amount, buyToken, rewardPool); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); _stake(buyToken, buyAmount, rewardPool); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Deposits the Underlying LP (e.g. Curve LP) into the pool /// @param intermediateToken The underlying LP token to deposit /// @param amount The quantity of intermediateToken to deposit /// @param buyToken The Convex like deposit token address (i.e. the cvxToken) /// @param rewardPool The base reward pool for the buyToken function _deposit( address intermediateToken, uint256 amount, address buyToken, IBaseRewardPool rewardPool ) internal returns (uint256 buyAmount) { uint256 pid = rewardPool.pid(); uint256 balance = _getBalance(address(this), buyToken); _approve(intermediateToken, address(BOOSTER), amount); BOOSTER.deposit(pid, amount, false); buyAmount = _getBalance(address(this), buyToken) - balance; } /// @notice Stakes the cvxToken into the reward pool /// @param buyToken The Convex like deposit token address (i.e. the cvxToken) /// @param buyAmount The quantity of buyToken to deposit /// @param rewardPool The base reward pool for the buyToken function _stake( address buyToken, uint256 buyAmount, IBaseRewardPool rewardPool ) internal { _approve(buyToken, address(rewardPool), buyAmount); rewardPool.stakeFor(msg.sender, buyAmount); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.11; interface IBaseRewardPool { function pid() external view returns (uint256); function stakeFor(address _for, uint256 _amount) external returns (bool); }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.11; interface IBooster { function deposit( uint256 _pid, uint256 _amount, bool _stake ) external returns (bool); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Compound like pools into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/ICtoken.sol"; /// Thrown when insufficient buyAmount is received after withdrawal /// @param buyAmount The amount of tokens received /// @param minBuyAmount The minimum acceptable quantity of buyAmount error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract CompoundPortalOut is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Remove liquidity from Compound like pools into network tokens/ERC20 tokens /// @param sellToken The Compound like market address (i.e. the cToken, fToken, etc.) /// @param sellAmount The quantity of sellToken to Portal out /// @param intermediateToken The intermediate token to swap to (i.e. the underlying token) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the swap /// @param data The encoded call for the swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); uint256 balance = _getBalance(address(this), intermediateToken); assert(ICtoken(sellToken).redeem(amount) == 0); amount = _getBalance(address(this), intermediateToken) - balance; buyAmount = _execute(intermediateToken, amount, buyToken, target, data); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyAmount = _getFeeAmount(buyAmount, fee); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface ICtoken { function mint(uint256 mintAmount) external returns (uint256); function mint() external payable; function redeem(uint256 redeemTokens) external returns (uint256); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Compound like pools (e.g. Rari Fuse) using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/ICtoken.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract CompoundPortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) {} /// @notice Add liquidity to Compound like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be one of the pool tokens) /// @param buyToken The Compound like market address (i.e. the cToken, fToken, etc.) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); uint256 balance = _getBalance(address(this), buyToken); if (intermediateToken == address(0)) ICtoken(buyToken).mint{ value: amount }(); else { _approve(intermediateToken, buyToken, amount); assert(ICtoken(buyToken).mint(amount) == 0); } buyAmount = _getBalance(address(this), buyToken) - balance; ERC20(buyToken).safeTransfer(msg.sender, buyAmount); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract removes liquidity from Balancer V2 like pools into any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IBalancerVault.sol"; /// Thrown when insufficient buyAmount is received after withdrawal /// @param buyAmount The amount of tokens received /// @param minBuyAmount The minimum acceptable quantity of buyAmount error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract BalancerV2PortalOut is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IBalancerVault public immutable VAULT; /// @notice Emitted when a portal is exited /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal out /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalOut( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IBalancerVault _vault ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { VAULT = _vault; } /// @notice Remove liquidity from Balancer V2 like pools into network tokens/ERC20 tokens /// @param sellToken The Balancer V2 pool address (i.e. the LP token address) /// @param sellAmount The quantity of sellToken to Portal out /// @param intermediateToken The intermediate token to swap from (must be one of the pool tokens) /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalOut( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, bytes calldata poolData ) external payable pausable returns (uint256 buyAmount) { sellAmount = _transferFromCaller(sellToken, sellAmount); uint256 intermediateAmount = _withdraw( sellToken, sellAmount, intermediateToken, poolData ); buyAmount = _execute( intermediateToken, intermediateAmount, buyToken, target, data ); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); buyAmount = _getFeeAmount(buyAmount, fee); buyToken == address(0) ? msg.sender.safeTransferETH(buyAmount) : ERC20(buyToken).safeTransfer(msg.sender, buyAmount); emit PortalOut( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Removes the intermediate token from the pool /// @param sellToken The pool address /// @param sellAmount The quantity of LP tokens to remove from the pool /// @param buyToken The ERC20 token being removed (i.e. the intermediate token) /// @param poolData Encoded pool data including the following: /// poolId The balancer pool ID /// assets An array of all tokens in the pool /// The index of the intermediate in the pool /// @return liquidity The quantity of LP tokens acquired function _withdraw( address sellToken, uint256 sellAmount, address buyToken, bytes calldata poolData ) internal returns (uint256) { (bytes32 poolId, address[] memory assets, uint256 index) = abi.decode( poolData, (bytes32, address[], uint256) ); uint256[] memory minAmountsOut = new uint256[](assets.length); bytes memory userData = abi.encode(0, sellAmount, index); uint256 balance = _getBalance(address(this), buyToken); _approve(sellToken, address(VAULT), sellAmount); VAULT.exitPool( poolId, address(this), payable(address(this)), IBalancerVault.ExitPoolRequest({ assets: assets, minAmountsOut: minAmountsOut, userData: userData, toInternalBalance: false }) ); return _getBalance(address(this), buyToken) - balance; } }
/// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; interface IBalancerVault { /** * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized * Pool shares. * * If the caller is not `sender`, it must be an authorized relayer for them. * * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces * these maximums. * * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent * back to the caller (not the sender, which is important for relayers). * * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final * `assets` array might not be sorted. Pools with no registered tokens cannot be joined. * * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be * withdrawn from Internal Balance: attempting to do so will trigger a revert. * * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement * their own custom logic. This typically requires additional information from the user (such as the expected number * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed * directly to the Pool's contract, as is `recipient`. * * Emits a `PoolBalanceChanged` event. */ function joinPool( bytes32 poolId, address sender, address recipient, JoinPoolRequest memory request ) external payable; struct JoinPoolRequest { address[] assets; uint256[] maxAmountsIn; bytes userData; bool fromInternalBalance; } /** * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see * `getPoolTokenInfo`). * * If the caller is not `sender`, it must be an authorized relayer for them. * * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault: * it just enforces these minimums. * * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit. * * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited. * * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise, * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to * do so will trigger a revert. * * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the * `tokens` array. This array must match the Pool's registered tokens. * * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement * their own custom logic. This typically requires additional information from the user (such as the expected number * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and * passed directly to the Pool's contract. * * Emits a `PoolBalanceChanged` event. */ function exitPool( bytes32 poolId, address sender, address payable recipient, ExitPoolRequest memory request ) external; struct ExitPoolRequest { address[] assets; uint256[] minAmountsOut; bytes userData; bool toInternalBalance; } function getPoolTokens(bytes32 poolId) external view returns ( address[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock ); }
/// Copyright (C) 2022 Portals.fi /// @author Portals.fi /// @notice This contract adds liquidity to Balancer V2 like pools using any ERC20 token or the network token. /// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.11; import "../base/PortalBaseV1.sol"; import "../interface/IPortalRegistry.sol"; import "./interface/IBalancerVault.sol"; /// Thrown when insufficient liquidity is received after deposit /// @param buyAmount The amount of liquidity received /// @param minBuyAmount The minimum acceptable quantity of liquidity received error InsufficientBuy(uint256 buyAmount, uint256 minBuyAmount); contract BalancerV2PortalIn is PortalBaseV1 { using SafeTransferLib for address; using SafeTransferLib for ERC20; IBalancerVault public immutable VAULT; /// @notice Emitted when a portal is entered /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param buyToken The ERC20 token address to buy (address(0) if network token) /// @param buyAmount The quantity of buyToken received /// @param fee The fee in BPS /// @param sender The msg.sender /// @param partner The front end operator address event PortalIn( address sellToken, uint256 sellAmount, address buyToken, uint256 buyAmount, uint256 fee, address indexed sender, address indexed partner ); constructor( bytes32 protocolId, PortalType portalType, IPortalRegistry registry, address exchange, address wrappedNetworkToken, uint256 fee, IBalancerVault _vault ) PortalBaseV1( protocolId, portalType, registry, exchange, wrappedNetworkToken, fee ) { VAULT = _vault; } /// @notice Add liquidity to Balancer V2 like pools with network tokens/ERC20 tokens /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to Portal in /// @param intermediateToken The intermediate token to swap to (must be one of the pool tokens) /// @param buyToken The Balancer V2 pool address (i.e. the LP token address) /// @param minBuyAmount The minimum quantity of buyTokens to receive. Reverts otherwise /// @param target The excecution target for the intermediate swap /// @param data The encoded call for the intermediate swap /// @param partner The front end operator address /// @return buyAmount The quantity of buyToken acquired function portalIn( address sellToken, uint256 sellAmount, address intermediateToken, address buyToken, uint256 minBuyAmount, address target, bytes calldata data, address partner, bytes calldata poolData ) external payable pausable returns (uint256 buyAmount) { uint256 amount = _transferFromCaller(sellToken, sellAmount); amount = _getFeeAmount(amount, fee); amount = _execute(sellToken, amount, intermediateToken, target, data); buyAmount = _deposit(intermediateToken, amount, buyToken, poolData); if (buyAmount < minBuyAmount) revert InsufficientBuy(buyAmount, minBuyAmount); emit PortalIn( sellToken, sellAmount, buyToken, buyAmount, fee, msg.sender, partner ); } /// @notice Deposits the sellToken into the pool /// @param sellToken The ERC20 token address to spend (address(0) if network token) /// @param sellAmount The quantity of sellToken to deposit /// @param buyToken The Balancer V2 pool token address /// @param poolData Encoded pool data including the following: /// poolId The balancer pool ID /// assets An array of all tokens in the pool /// The index of the sellToken in the pool /// @return liquidity The quantity of LP tokens acquired function _deposit( address sellToken, uint256 sellAmount, address buyToken, bytes calldata poolData ) internal returns (uint256) { (bytes32 poolId, address[] memory assets, uint256 index) = abi.decode( poolData, (bytes32, address[], uint256) ); uint256[] memory maxAmountsIn = new uint256[](assets.length); maxAmountsIn[index] = sellAmount; bytes memory userData = abi.encode(1, maxAmountsIn, 0); uint256 balance = _getBalance(msg.sender, buyToken); uint256 valueToSend; if (sellToken == address(0)) { valueToSend = sellAmount; } else { _approve(sellToken, address(VAULT), sellAmount); } VAULT.joinPool{ value: valueToSend }( poolId, address(this), msg.sender, IBalancerVault.JoinPoolRequest({ assets: assets, maxAmountsIn: maxAmountsIn, userData: userData, fromInternalBalance: false }) ); return _getBalance(msg.sender, buyToken) - balance; } }
{ "optimizer": { "enabled": true, "runs": 1000 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "metadata": { "useLiteralContent": true } }
[{"inputs":[{"internalType":"address","name":"_collector","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"uint96","name":"version","type":"uint96"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct PortalRegistryV1.Portal","name":"portal","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"numPortals","type":"uint256"}],"name":"AddPortal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"uint96","name":"version","type":"uint96"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct PortalRegistryV1.Portal","name":"portal","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"numPortals","type":"uint256"}],"name":"RemovePortal","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"uint96","name":"version","type":"uint96"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct PortalRegistryV1.Portal","name":"portal","type":"tuple"},{"indexed":false,"internalType":"uint256","name":"numPortals","type":"uint256"}],"name":"UpdatePortal","type":"event"},{"inputs":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"}],"name":"addPortal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_bytes","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"collector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"}],"name":"getAllPortals","outputs":[{"components":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"uint96","name":"version","type":"uint96"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct PortalRegistryV1.Portal[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"enum PortalRegistryV1.PortalType","name":"","type":"uint8"}],"name":"getPortalById","outputs":[{"internalType":"address","name":"portal","type":"address"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"},{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"uint96","name":"version","type":"uint96"},{"internalType":"bool","name":"active","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSupportedPlatforms","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isPortal","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numPortals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"partners","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"registrars","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"protocolId","type":"bytes32"},{"internalType":"enum PortalRegistryV1.PortalType","name":"portalType","type":"uint8"}],"name":"removePortal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_string","type":"string"}],"name":"stringToBytes32","outputs":[{"internalType":"bytes32","name":"_bytes32String","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collector","type":"address"}],"name":"updateCollector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"partner","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"name":"updatePartners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"registrar","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"name":"updateRegistrars","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
60806040523480156200001157600080fd5b50604051620014b8380380620014b88339810160408190526200003491620001e1565b6200003f336200009f565b600180546001600160a01b0319166001600160a01b0384811691909117825533600090815260026020526040808220805460ff19908116861790915592851682529020805490911690911790556200009781620000ef565b505062000219565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000546001600160a01b031633146200014f5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6001600160a01b038116620001b65760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840162000146565b620001c1816200009f565b50565b80516001600160a01b0381168114620001dc57600080fd5b919050565b60008060408385031215620001f557600080fd5b6200020083620001c4565b91506200021060208401620001c4565b90509250929050565b61128f80620002296000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c806393cb4c85116100b2578063dbb6eeba11610081578063e37ba62211610066578063e37ba6221461032e578063ed30d98714610341578063f2fde38b1461035657600080fd5b8063dbb6eeba14610312578063dc54e9131461031b57600080fd5b806393cb4c851461023e57806395c33652146102b9578063c316c98b146102dc578063cfb51928146102ef57600080fd5b8063715018a6116101095780638da5cb5b116100ee5780638da5cb5b146101e6578063913e77ad1461020b5780639201de551461021e57600080fd5b8063715018a6146101bb57806389aeca76146101c357600080fd5b806313eb46711461013b5780631b77b95714610173578063556e6cc3146101885780636bc30ff8146101a8575b600080fd5b61015e610149366004610deb565b60036020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b610186610181366004610e0d565b610369565b005b61019b610196366004610e58565b6103f3565b60405161016a9190610efd565b6101866101b6366004610f4b565b6105a7565b6101866106bf565b61015e6101d1366004610deb565b60026020526000908152604090205460ff1681565b6000546001600160a01b03165b6040516001600160a01b03909116815260200161016a565b6001546101f3906001600160a01b031681565b61023161022c366004610f77565b610725565b60405161016a9190610f90565b6102a861024c366004610f4b565b60056020908152600092835260408084209091529082529020805460018201546002909201546001600160a01b0382169260ff600160a01b90930483169290916bffffffffffffffffffffffff811691600160601b9091041685565b60405161016a959493929190610fe5565b61015e6102c7366004610deb565b60046020526000908152604090205460ff1681565b6101866102ea366004610deb565b610750565b6103046102fd366004611044565b6020015190565b60405190815260200161016a565b61030460075481565b6101866103293660046110f5565b6107d9565b61018661033c366004610e0d565b610bb3565b610349610c38565b60405161016a9190611131565b610186610364366004610deb565b610c90565b6000546001600160a01b031633146103c85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6001600160a01b03919091166000908152600460205260409020805460ff1916911515919091179055565b6060600060075467ffffffffffffffff8111156104125761041261102e565b60405190808252806020026020018201604052801561046b57816020015b6040805160a0810182526000808252602080830182905292820181905260608201819052608082015282526000199092019101816104305790505b50905060005b6006548110156105a0576000600560006006848154811061049457610494611169565b9060005260206000200154815260200190815260200160002060008660018111156104c1576104c1610e73565b60018111156104d2576104d2610e73565b81526020808201929092526040908101600020815160a0810190925280546001600160a01b03811683529192909190830190600160a01b900460ff16600181111561051f5761051f610e73565b600181111561053057610530610e73565b8152600182015460208201526002909101546bffffffffffffffffffffffff81166040830152600160601b900460ff1615156060909101528351909150819084908490811061058157610581611169565b602002602001018190525050808061059890611195565b915050610471565b5092915050565b6000546001600160a01b031633146106015760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b60008281526005602052604081208183600181111561062257610622610e73565b600181111561063357610633610e73565b8152602080820192909252604090810160009081206002810180546cff0000000000000000000000001916905580546001600160a01b031682526003909352819020805460ff1916905560075490519192507f4eb9c9ab2debb9a462ef06380881f9efe5b23c9f4f0c32c0db22cda0829e2892916106b29184916111b0565b60405180910390a1505050565b6000546001600160a01b031633146107195760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6107236000610d72565b565b60608160405160200161073a91815260200190565b6040516020818303038152906040529050919050565b6000546001600160a01b031633146107aa5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b3260009081526002602052604090205460ff166108385760405162461bcd60e51b815260206004820152600e60248201527f496e76616c6964206f726967696e00000000000000000000000000000000000060448201526064016103bf565b60008181526005602052604081208184600181111561085957610859610e73565b600181111561086a5761086a610e73565b8152602081019190915260400160002060028101549091506bffffffffffffffffffffffff16156109a35780546001600160a01b039081166000908152600360205260408120805460ff19169055825473ffffffffffffffffffffffffffffffffffffffff19169186169190911782556002820180546bffffffffffffffffffffffff16916108f883611212565b82546bffffffffffffffffffffffff9182166101009390930a9283029190920219909116179055506002810180546cff0000000000000000000000001916600160601b1790556001600160a01b03841660009081526003602052604090819020805460ff1916600117905560075490517f383c016bd8b28d085a26a452be0a14fefad6ede3e93d1ce5cf4d11617f82ccd591610996918491906111b0565b60405180910390a1610bad565b60006040518060a00160405280866001600160a01b031681526020018560018111156109d1576109d1610e73565b815260200184815260200160016bffffffffffffffffffffffff16815260200160011515815250905080600560008581526020019081526020016000206000866001811115610a2257610a22610e73565b6001811115610a3357610a33610e73565b8152602080820192909252604001600020825181546001600160a01b0390911673ffffffffffffffffffffffffffffffffffffffff1982168117835592840151919283917fffffffffffffffffffffff0000000000000000000000000000000000000000001617600160a01b836001811115610ab157610ab1610e73565b02179055506040828101516001808401919091556060840151600290930180546080909501511515600160601b026cffffffffffffffffffffffffff199095166bffffffffffffffffffffffff90941693909317939093179091556001600160a01b03871660009081526003602052908120805460ff19168317905560068054928301815581527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f909101849055600780547fd7e8e120f6539f32cfef91e2f24e4ed32f54131fed5aca9bc290963dd4e7748c92849290610b9183611195565b91905055604051610ba392919061123e565b60405180910390a1505b50505050565b6000546001600160a01b03163314610c0d5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001600160a01b03919091166000908152600260205260409020805460ff1916911515919091179055565b60606006805480602002602001604051908101604052809291908181526020018280548015610c8657602002820191906000526020600020905b815481526020019060010190808311610c72575b5050505050905090565b6000546001600160a01b03163314610cea5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001600160a01b038116610d665760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103bf565b610d6f81610d72565b50565b600080546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b0381168114610de657600080fd5b919050565b600060208284031215610dfd57600080fd5b610e0682610dcf565b9392505050565b60008060408385031215610e2057600080fd5b610e2983610dcf565b915060208301358015158114610e3e57600080fd5b809150509250929050565b803560028110610de657600080fd5b600060208284031215610e6a57600080fd5b610e0682610e49565b634e487b7160e01b600052602160045260246000fd5b60028110610ea757634e487b7160e01b600052602160045260246000fd5b9052565b6001600160a01b0381511682526020810151610eca6020840182610e89565b50604081015160408301526bffffffffffffffffffffffff60608201511660608301526080810151151560808301525050565b6020808252825182820181905260009190848201906040850190845b81811015610f3f57610f2c838551610eab565b9284019260a09290920191600101610f19565b50909695505050505050565b60008060408385031215610f5e57600080fd5b82359150610f6e60208401610e49565b90509250929050565b600060208284031215610f8957600080fd5b5035919050565b600060208083528351808285015260005b81811015610fbd57858101830151858201604001528201610fa1565b81811115610fcf576000604083870101525b50601f01601f1916929092016040019392505050565b6001600160a01b038616815260a081016110026020830187610e89565b8460408301526bffffffffffffffffffffffff8416606083015282151560808301529695505050505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561105657600080fd5b813567ffffffffffffffff8082111561106e57600080fd5b818401915084601f83011261108257600080fd5b8135818111156110945761109461102e565b604051601f8201601f19908116603f011681019083821181831017156110bc576110bc61102e565b816040528281528760208487010111156110d557600080fd5b826020860160208301376000928101602001929092525095945050505050565b60008060006060848603121561110a57600080fd5b61111384610dcf565b925061112160208501610e49565b9150604084013590509250925092565b6020808252825182820181905260009190848201906040850190845b81811015610f3f5783518352928401929184019160010161114d565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006000198214156111a9576111a961117f565b5060010190565b82546001600160a01b038116825260c08201906111d76020840160a083901c60ff16610e89565b50600184015460408301526002909301546bffffffffffffffffffffffff81166060808401919091521c60ff161515608082015260a0015290565b60006bffffffffffffffffffffffff808316818114156112345761123461117f565b6001019392505050565b60c0810161124c8285610eab565b8260a0830152939250505056fea2646970667358221220b349c83422edf55ffc91311b91bf101598d74a166e0cb39249e9feb3245b39db64736f6c634300080b00330000000000000000000000008a37e1808d34f75db5807198c1a627e048813831000000000000000000000000a7d040c780a84a18dbd8f47a6beca2aa17a60ea3
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106101365760003560e01c806393cb4c85116100b2578063dbb6eeba11610081578063e37ba62211610066578063e37ba6221461032e578063ed30d98714610341578063f2fde38b1461035657600080fd5b8063dbb6eeba14610312578063dc54e9131461031b57600080fd5b806393cb4c851461023e57806395c33652146102b9578063c316c98b146102dc578063cfb51928146102ef57600080fd5b8063715018a6116101095780638da5cb5b116100ee5780638da5cb5b146101e6578063913e77ad1461020b5780639201de551461021e57600080fd5b8063715018a6146101bb57806389aeca76146101c357600080fd5b806313eb46711461013b5780631b77b95714610173578063556e6cc3146101885780636bc30ff8146101a8575b600080fd5b61015e610149366004610deb565b60036020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b610186610181366004610e0d565b610369565b005b61019b610196366004610e58565b6103f3565b60405161016a9190610efd565b6101866101b6366004610f4b565b6105a7565b6101866106bf565b61015e6101d1366004610deb565b60026020526000908152604090205460ff1681565b6000546001600160a01b03165b6040516001600160a01b03909116815260200161016a565b6001546101f3906001600160a01b031681565b61023161022c366004610f77565b610725565b60405161016a9190610f90565b6102a861024c366004610f4b565b60056020908152600092835260408084209091529082529020805460018201546002909201546001600160a01b0382169260ff600160a01b90930483169290916bffffffffffffffffffffffff811691600160601b9091041685565b60405161016a959493929190610fe5565b61015e6102c7366004610deb565b60046020526000908152604090205460ff1681565b6101866102ea366004610deb565b610750565b6103046102fd366004611044565b6020015190565b60405190815260200161016a565b61030460075481565b6101866103293660046110f5565b6107d9565b61018661033c366004610e0d565b610bb3565b610349610c38565b60405161016a9190611131565b610186610364366004610deb565b610c90565b6000546001600160a01b031633146103c85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6001600160a01b03919091166000908152600460205260409020805460ff1916911515919091179055565b6060600060075467ffffffffffffffff8111156104125761041261102e565b60405190808252806020026020018201604052801561046b57816020015b6040805160a0810182526000808252602080830182905292820181905260608201819052608082015282526000199092019101816104305790505b50905060005b6006548110156105a0576000600560006006848154811061049457610494611169565b9060005260206000200154815260200190815260200160002060008660018111156104c1576104c1610e73565b60018111156104d2576104d2610e73565b81526020808201929092526040908101600020815160a0810190925280546001600160a01b03811683529192909190830190600160a01b900460ff16600181111561051f5761051f610e73565b600181111561053057610530610e73565b8152600182015460208201526002909101546bffffffffffffffffffffffff81166040830152600160601b900460ff1615156060909101528351909150819084908490811061058157610581611169565b602002602001018190525050808061059890611195565b915050610471565b5092915050565b6000546001600160a01b031633146106015760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b60008281526005602052604081208183600181111561062257610622610e73565b600181111561063357610633610e73565b8152602080820192909252604090810160009081206002810180546cff0000000000000000000000001916905580546001600160a01b031682526003909352819020805460ff1916905560075490519192507f4eb9c9ab2debb9a462ef06380881f9efe5b23c9f4f0c32c0db22cda0829e2892916106b29184916111b0565b60405180910390a1505050565b6000546001600160a01b031633146107195760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6107236000610d72565b565b60608160405160200161073a91815260200190565b6040516020818303038152906040529050919050565b6000546001600160a01b031633146107aa5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b3260009081526002602052604090205460ff166108385760405162461bcd60e51b815260206004820152600e60248201527f496e76616c6964206f726967696e00000000000000000000000000000000000060448201526064016103bf565b60008181526005602052604081208184600181111561085957610859610e73565b600181111561086a5761086a610e73565b8152602081019190915260400160002060028101549091506bffffffffffffffffffffffff16156109a35780546001600160a01b039081166000908152600360205260408120805460ff19169055825473ffffffffffffffffffffffffffffffffffffffff19169186169190911782556002820180546bffffffffffffffffffffffff16916108f883611212565b82546bffffffffffffffffffffffff9182166101009390930a9283029190920219909116179055506002810180546cff0000000000000000000000001916600160601b1790556001600160a01b03841660009081526003602052604090819020805460ff1916600117905560075490517f383c016bd8b28d085a26a452be0a14fefad6ede3e93d1ce5cf4d11617f82ccd591610996918491906111b0565b60405180910390a1610bad565b60006040518060a00160405280866001600160a01b031681526020018560018111156109d1576109d1610e73565b815260200184815260200160016bffffffffffffffffffffffff16815260200160011515815250905080600560008581526020019081526020016000206000866001811115610a2257610a22610e73565b6001811115610a3357610a33610e73565b8152602080820192909252604001600020825181546001600160a01b0390911673ffffffffffffffffffffffffffffffffffffffff1982168117835592840151919283917fffffffffffffffffffffff0000000000000000000000000000000000000000001617600160a01b836001811115610ab157610ab1610e73565b02179055506040828101516001808401919091556060840151600290930180546080909501511515600160601b026cffffffffffffffffffffffffff199095166bffffffffffffffffffffffff90941693909317939093179091556001600160a01b03871660009081526003602052908120805460ff19168317905560068054928301815581527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f909101849055600780547fd7e8e120f6539f32cfef91e2f24e4ed32f54131fed5aca9bc290963dd4e7748c92849290610b9183611195565b91905055604051610ba392919061123e565b60405180910390a1505b50505050565b6000546001600160a01b03163314610c0d5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001600160a01b03919091166000908152600260205260409020805460ff1916911515919091179055565b60606006805480602002602001604051908101604052809291908181526020018280548015610c8657602002820191906000526020600020905b815481526020019060010190808311610c72575b5050505050905090565b6000546001600160a01b03163314610cea5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103bf565b6001600160a01b038116610d665760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103bf565b610d6f81610d72565b50565b600080546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b0381168114610de657600080fd5b919050565b600060208284031215610dfd57600080fd5b610e0682610dcf565b9392505050565b60008060408385031215610e2057600080fd5b610e2983610dcf565b915060208301358015158114610e3e57600080fd5b809150509250929050565b803560028110610de657600080fd5b600060208284031215610e6a57600080fd5b610e0682610e49565b634e487b7160e01b600052602160045260246000fd5b60028110610ea757634e487b7160e01b600052602160045260246000fd5b9052565b6001600160a01b0381511682526020810151610eca6020840182610e89565b50604081015160408301526bffffffffffffffffffffffff60608201511660608301526080810151151560808301525050565b6020808252825182820181905260009190848201906040850190845b81811015610f3f57610f2c838551610eab565b9284019260a09290920191600101610f19565b50909695505050505050565b60008060408385031215610f5e57600080fd5b82359150610f6e60208401610e49565b90509250929050565b600060208284031215610f8957600080fd5b5035919050565b600060208083528351808285015260005b81811015610fbd57858101830151858201604001528201610fa1565b81811115610fcf576000604083870101525b50601f01601f1916929092016040019392505050565b6001600160a01b038616815260a081016110026020830187610e89565b8460408301526bffffffffffffffffffffffff8416606083015282151560808301529695505050505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561105657600080fd5b813567ffffffffffffffff8082111561106e57600080fd5b818401915084601f83011261108257600080fd5b8135818111156110945761109461102e565b604051601f8201601f19908116603f011681019083821181831017156110bc576110bc61102e565b816040528281528760208487010111156110d557600080fd5b826020860160208301376000928101602001929092525095945050505050565b60008060006060848603121561110a57600080fd5b61111384610dcf565b925061112160208501610e49565b9150604084013590509250925092565b6020808252825182820181905260009190848201906040850190845b81811015610f3f5783518352928401929184019160010161114d565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006000198214156111a9576111a961117f565b5060010190565b82546001600160a01b038116825260c08201906111d76020840160a083901c60ff16610e89565b50600184015460408301526002909301546bffffffffffffffffffffffff81166060808401919091521c60ff161515608082015260a0015290565b60006bffffffffffffffffffffffff808316818114156112345761123461117f565b6001019392505050565b60c0810161124c8285610eab565b8260a0830152939250505056fea2646970667358221220b349c83422edf55ffc91311b91bf101598d74a166e0cb39249e9feb3245b39db64736f6c634300080b0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000008a37e1808d34f75db5807198c1a627e048813831000000000000000000000000a7d040c780a84a18dbd8f47a6beca2aa17a60ea3
-----Decoded View---------------
Arg [0] : _collector (address): 0x8A37E1808D34f75Db5807198c1A627e048813831
Arg [1] : _owner (address): 0xa7D040C780A84A18DbD8F47a6beCa2aA17A60ea3
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000008a37e1808d34f75db5807198c1a627e048813831
Arg [1] : 000000000000000000000000a7d040c780a84a18dbd8f47a6beca2aa17a60ea3
Deployed ByteCode Sourcemap
226:5937:31:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;499:40;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;571:14:46;;564:22;546:41;;534:2;519:18;499:40:31;;;;;;;;5295:116;;;;;;:::i;:::-;;:::i;:::-;;4099:430;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;3636:330::-;;;;;;:::i;:::-;;:::i;1668:101:0:-;;;:::i;377:42:31:-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;1036:85:0;1082:7;1108:6;-1:-1:-1;;;;;1108:6:0;1036:85;;;-1:-1:-1;;;;;3439:55:46;;;3421:74;;3409:2;3394:18;1036:85:0;3275:226:46;307:24:31;;;;;-1:-1:-1;;;;;307:24:31;;;6004:157;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;710:70::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;710:70:31;;;;-1:-1:-1;;;710:70:31;;;;;;;;;;;;-1:-1:-1;;;710:70:31;;;;;;;;;;;;;;;;;:::i;596:40::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;5524:103;;;;;;:::i;:::-;;:::i;5709:213::-;;;;;;:::i;:::-;5902:2;5889:16;5883:23;;5709:213;;;;6146:25:46;;;6134:2;6119:18;5709:213:31;6000:177:46;907:25:31;;;;;;2492:944;;;;;;:::i;:::-;;:::i;4935:144::-;;;;;;:::i;:::-;;:::i;4595:116::-;;;:::i;:::-;;;;;;;:::i;1918:198:0:-;;;;;;:::i;:::-;;:::i;5295:116:31:-;1082:7:0;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;;;;;;;;;-1:-1:-1;;;;;5378:17:31;;;::::1;;::::0;;;:8:::1;:17;::::0;;;;:26;;-1:-1:-1;;5378:26:31::1;::::0;::::1;;::::0;;;::::1;::::0;;5295:116::o;4099:430::-;4192:15;4223:23;4262:10;;4249:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4249:24:31;;-1:-1:-1;;4249:24:31;;;;;;;;;;;;4223:50;;4288:9;4283:216;4307:18;:25;4303:29;;4283:216;;;4353:20;4376:13;:36;4390:18;4409:1;4390:21;;;;;;;;:::i;:::-;;;;;;;;;4376:36;;;;;;;;;;;:78;4430:10;4376:78;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;-1:-1:-1;4376:78:31;4353:101;;;;;;;;;;-1:-1:-1;;;;;4353:101:31;;;;;;4376:78;;4353:101;;;;-1:-1:-1;;;4353:101:31;;;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;4353:101:31;;;;;;;;;;;4469:10;;4353:101;;-1:-1:-1;4353:101:31;;4469:10;;4477:1;;4469:10;;;;;;:::i;:::-;;;;;;:19;;;;4339:160;4334:3;;;;;:::i;:::-;;;;4283:216;;;-1:-1:-1;4515:7:31;4099:430;-1:-1:-1;;4099:430:31:o;3636:330::-;1082:7:0;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;7357:356:46;1240:68:0;3750:28:31::1;3781:25:::0;;;:13:::1;:25;::::0;;;;3750:28;3807:10;3781:37:::1;::::0;::::1;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;::::0;;::::1;::::0;;::::1;::::0;;;;;;;;-1:-1:-1;3781:37:31;;;3828:20:::1;::::0;::::1;:28:::0;;-1:-1:-1;;3828:28:31::1;::::0;;3875:20;;-1:-1:-1;;;;;3875:20:31::1;3866:30:::0;;:8:::1;:30:::0;;;;;;:38;;-1:-1:-1;;3866:38:31::1;::::0;;3948:10:::1;::::0;3920:39;;3781:37;;-1:-1:-1;3920:39:31::1;::::0;::::1;::::0;3781:37;;3920:39:::1;:::i;:::-;;;;;;;;3740:226;3636:330:::0;;:::o;1668:101:0:-;1082:7;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;7357:356:46;1240:68:0;1732:30:::1;1759:1;1732:18;:30::i;:::-;1668:101::o:0;6004:157:31:-;6092:13;6146:6;6135:18;;;;;;6146:25:46;;6134:2;6119:18;;6000:177;6135:18:31;;;;;;;;;;;;;6121:33;;6004:157;;;:::o;5524:103::-;1082:7:0;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;7357:356:46;1240:68:0;5598:9:31::1;:22:::0;;-1:-1:-1;;5598:22:31::1;-1:-1:-1::0;;;;;5598:22:31;;;::::1;::::0;;;::::1;::::0;;5524:103::o;2492:944::-;1984:9;1973:21;;;;:10;:21;;;;;;;;1965:48;;;;-1:-1:-1;;;1965:48:31;;9208:2:46;1965:48:31;;;9190:21:46;9247:2;9227:18;;;9220:30;9286:16;9266:18;;;9259:44;9320:18;;1965:48:31;9006:338:46;1965:48:31;2634:29:::1;2666:25:::0;;;:13:::1;:25;::::0;;;;2634:29;2692:10;2666:37:::1;::::0;::::1;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;::::0;;::::1;::::0;::::1;::::0;;;;;;-1:-1:-1;2666:37:31;2717:22:::1;::::0;::::1;::::0;2666:37;;-1:-1:-1;2717:22:31::1;;:27:::0;2713:717:::1;;2769:21:::0;;-1:-1:-1;;;;;2769:21:31;;::::1;2794:5;2760:31:::0;;;:8:::1;:31;::::0;;;;:39;;-1:-1:-1;;2760:39:31::1;::::0;;2813:30;;-1:-1:-1;;2813:30:31::1;::::0;;::::1;::::0;;;::::1;::::0;;2857:22:::1;::::0;::::1;:24:::0;;::::1;;::::0;::::1;::::0;::::1;:::i;:::-;::::0;;::::1;::::0;;::::1;;::::0;;;::::1;::::0;;::::1;::::0;;;::::1;;::::0;;::::1;;::::0;;-1:-1:-1;2895:21:31::1;::::0;::::1;:28:::0;;-1:-1:-1;;2895:28:31::1;-1:-1:-1::0;;;2895:28:31::1;::::0;;-1:-1:-1;;;;;2937:16:31;::::1;-1:-1:-1::0;2937:16:31;;;:8:::1;:16;::::0;;;;;;:23;;-1:-1:-1;;2937:23:31::1;-1:-1:-1::0;2937:23:31::1;::::0;;3008:10:::1;::::0;2979:40;;::::1;::::0;::::1;::::0;2895:14;;3008:10;2979:40:::1;:::i;:::-;;;;;;;;2713:717;;;3050:23;3076:141;;;;;;;;3100:6;-1:-1:-1::0;;;;;3076:141:31::1;;;;;3124:10;3076:141;;;;;;;;:::i;:::-;;;;;3152:10;3076:141;;;;3180:1;3076:141;;;;;;3199:4;3076:141;;;;::::0;3050:167:::1;;3271:9;3231:13;:25;3245:10;3231:25;;;;;;;;;;;:37;3257:10;3231:37;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;::::0;;::::1;::::0;;::::1;::::0;;;;;;-1:-1:-1;3231:37:31;:49;;;;-1:-1:-1;;;;;3231:49:31;;::::1;-1:-1:-1::0;;3231:49:31;::::1;::::0;::::1;::::0;;;;::::1;::::0;:37;;;;:49;;;-1:-1:-1;;;3231:49:31;;;::::1;;;;;;:::i;:::-;;;::::0;;-1:-1:-1;3231:49:31::1;::::0;;::::1;::::0;::::1;::::0;;::::1;::::0;;;;::::1;::::0;::::1;::::0;::::1;::::0;;::::1;::::0;;::::1;::::0;;::::1;::::0;::::1;;-1:-1:-1::0;;;3231:49:31::1;-1:-1:-1::0;;3231:49:31;;;::::1;::::0;;::::1;::::0;;;;;;;::::1;::::0;;;-1:-1:-1;;;;;3294:16:31;::::1;3231:49;3294:16:::0;;;:8:::1;:16;::::0;;;;:23;;-1:-1:-1;;3294:23:31::1;::::0;::::1;::::0;;3331:18:::1;:35:::0;;;;::::1;::::0;;;;;;;::::1;::::0;;;3406:10:::1;:12:::0;;3385:34:::1;::::0;3395:9;;3406:10;:12:::1;::::0;::::1;:::i;:::-;;;;;3385:34;;;;;;;:::i;:::-;;;;;;;;3036:394;2713:717;2624:812;2492:944:::0;;;:::o;4935:144::-;1082:7:0;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;7357:356:46;1240:68:0;-1:-1:-1;;;;;5042:21:31;;;::::1;;::::0;;;:10:::1;:21;::::0;;;;:30;;-1:-1:-1;;5042:30:31::1;::::0;::::1;;::::0;;;::::1;::::0;;4935:144::o;4595:116::-;4651:16;4686:18;4679:25;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4595:116;:::o;1918:198:0:-;1082:7;1108:6;-1:-1:-1;;;;;1108:6:0;719:10:1;1248:23:0;1240:68;;;;-1:-1:-1;;;1240:68:0;;7559:2:46;1240:68:0;;;7541:21:46;;;7578:18;;;7571:30;7637:34;7617:18;;;7610:62;7689:18;;1240:68:0;7357:356:46;1240:68:0;-1:-1:-1;;;;;2006:22:0;::::1;1998:73;;;::::0;-1:-1:-1;;;1998:73:0;;10094:2:46;1998:73:0::1;::::0;::::1;10076:21:46::0;10133:2;10113:18;;;10106:30;10172:34;10152:18;;;10145:62;10243:8;10223:18;;;10216:36;10269:19;;1998:73:0::1;9892:402:46::0;1998:73:0::1;2081:28;2100:8;2081:18;:28::i;:::-;1918:198:::0;:::o;2270:187::-;2343:16;2362:6;;-1:-1:-1;;;;;2378:17:0;;;-1:-1:-1;;2378:17:0;;;;;;2410:40;;2362:6;;;;;;;2410:40;;2343:16;2410:40;2333:124;2270:187;:::o;14:196:46:-;82:20;;-1:-1:-1;;;;;131:54:46;;121:65;;111:93;;200:1;197;190:12;111:93;14:196;;;:::o;215:186::-;274:6;327:2;315:9;306:7;302:23;298:32;295:52;;;343:1;340;333:12;295:52;366:29;385:9;366:29;:::i;:::-;356:39;215:186;-1:-1:-1;;;215:186:46:o;598:347::-;663:6;671;724:2;712:9;703:7;699:23;695:32;692:52;;;740:1;737;730:12;692:52;763:29;782:9;763:29;:::i;:::-;753:39;;842:2;831:9;827:18;814:32;889:5;882:13;875:21;868:5;865:32;855:60;;911:1;908;901:12;855:60;934:5;924:15;;;598:347;;;;;:::o;950:151::-;1026:20;;1075:1;1065:12;;1055:40;;1091:1;1088;1081:12;1106:209;1180:6;1233:2;1221:9;1212:7;1208:23;1204:32;1201:52;;;1249:1;1246;1239:12;1201:52;1272:37;1299:9;1272:37;:::i;1320:184::-;-1:-1:-1;;;1369:1:46;1362:88;1469:4;1466:1;1459:15;1493:4;1490:1;1483:15;1509:295;1591:1;1584:5;1581:12;1571:200;;-1:-1:-1;;;1624:1:46;1617:88;1728:4;1725:1;1718:15;1756:4;1753:1;1746:15;1571:200;1780:18;;1509:295::o;1809:474::-;-1:-1:-1;;;;;1891:5:46;1885:12;1881:61;1876:3;1869:74;1989:4;1982:5;1978:16;1972:23;2004:56;2054:4;2049:3;2045:14;2031:12;2004:56;:::i;:::-;;2109:4;2102:5;2098:16;2092:23;2085:4;2080:3;2076:14;2069:47;2177:26;2169:4;2162:5;2158:16;2152:23;2148:56;2141:4;2136:3;2132:14;2125:80;2268:4;2261:5;2257:16;2251:23;2244:31;2237:39;2230:4;2225:3;2221:14;2214:63;1809:474;;:::o;2288:700::-;2507:2;2559:21;;;2629:13;;2532:18;;;2651:22;;;2478:4;;2507:2;2730:15;;;;2704:2;2689:18;;;2478:4;2773:189;2787:6;2784:1;2781:13;2773:189;;;2836:44;2876:3;2867:6;2861:13;2836:44;:::i;:::-;2937:15;;;;2909:4;2900:14;;;;;2809:1;2802:9;2773:189;;;-1:-1:-1;2979:3:46;;2288:700;-1:-1:-1;;;;;;2288:700:46:o;2993:277::-;3076:6;3084;3137:2;3125:9;3116:7;3112:23;3108:32;3105:52;;;3153:1;3150;3143:12;3105:52;3189:9;3176:23;3166:33;;3218:46;3260:2;3249:9;3245:18;3218:46;:::i;:::-;3208:56;;2993:277;;;;;:::o;3506:180::-;3565:6;3618:2;3606:9;3597:7;3593:23;3589:32;3586:52;;;3634:1;3631;3624:12;3586:52;-1:-1:-1;3657:23:46;;3506:180;-1:-1:-1;3506:180:46:o;3691:597::-;3803:4;3832:2;3861;3850:9;3843:21;3893:6;3887:13;3936:6;3931:2;3920:9;3916:18;3909:34;3961:1;3971:140;3985:6;3982:1;3979:13;3971:140;;;4080:14;;;4076:23;;4070:30;4046:17;;;4065:2;4042:26;4035:66;4000:10;;3971:140;;;4129:6;4126:1;4123:13;4120:91;;;4199:1;4194:2;4185:6;4174:9;4170:22;4166:31;4159:42;4120:91;-1:-1:-1;4272:2:46;4251:15;-1:-1:-1;;4247:29:46;4232:45;;;;4279:2;4228:54;;3691:597;-1:-1:-1;;;3691:597:46:o;4293:586::-;-1:-1:-1;;;;;4575:55:46;;4557:74;;4544:3;4529:19;;4640:54;4690:2;4675:18;;4667:6;4640:54;:::i;:::-;4730:6;4725:2;4714:9;4710:18;4703:34;4785:26;4777:6;4773:39;4768:2;4757:9;4753:18;4746:67;4864:6;4857:14;4850:22;4844:3;4833:9;4829:19;4822:51;4293:586;;;;;;;;:::o;4884:184::-;-1:-1:-1;;;4933:1:46;4926:88;5033:4;5030:1;5023:15;5057:4;5054:1;5047:15;5073:922;5142:6;5195:2;5183:9;5174:7;5170:23;5166:32;5163:52;;;5211:1;5208;5201:12;5163:52;5251:9;5238:23;5280:18;5321:2;5313:6;5310:14;5307:34;;;5337:1;5334;5327:12;5307:34;5375:6;5364:9;5360:22;5350:32;;5420:7;5413:4;5409:2;5405:13;5401:27;5391:55;;5442:1;5439;5432:12;5391:55;5478:2;5465:16;5500:2;5496;5493:10;5490:36;;;5506:18;;:::i;:::-;5581:2;5575:9;5549:2;5635:13;;-1:-1:-1;;5631:22:46;;;5655:2;5627:31;5623:40;5611:53;;;5679:18;;;5699:22;;;5676:46;5673:72;;;5725:18;;:::i;:::-;5765:10;5761:2;5754:22;5800:2;5792:6;5785:18;5840:7;5835:2;5830;5826;5822:11;5818:20;5815:33;5812:53;;;5861:1;5858;5851:12;5812:53;5917:2;5912;5908;5904:11;5899:2;5891:6;5887:15;5874:46;5962:1;5940:15;;;5957:2;5936:24;5929:35;;;;-1:-1:-1;5944:6:46;5073:922;-1:-1:-1;;;;;5073:922:46:o;6364:351::-;6456:6;6464;6472;6525:2;6513:9;6504:7;6500:23;6496:32;6493:52;;;6541:1;6538;6531:12;6493:52;6564:29;6583:9;6564:29;:::i;:::-;6554:39;;6612:46;6654:2;6643:9;6639:18;6612:46;:::i;:::-;6602:56;;6705:2;6694:9;6690:18;6677:32;6667:42;;6364:351;;;;;:::o;6720:632::-;6891:2;6943:21;;;7013:13;;6916:18;;;7035:22;;;6862:4;;6891:2;7114:15;;;;7088:2;7073:18;;;6862:4;7157:169;7171:6;7168:1;7165:13;7157:169;;;7232:13;;7220:26;;7301:15;;;;7266:12;;;;7193:1;7186:9;7157:169;;7718:184;-1:-1:-1;;;7767:1:46;7760:88;7867:4;7864:1;7857:15;7891:4;7888:1;7881:15;7907:184;-1:-1:-1;;;7956:1:46;7949:88;8056:4;8053:1;8046:15;8080:4;8077:1;8070:15;8096:135;8135:3;-1:-1:-1;;8156:17:46;;8153:43;;;8176:18;;:::i;:::-;-1:-1:-1;8223:1:46;8212:13;;8096:135::o;8236:765::-;8477:13;;-1:-1:-1;;;;;8517:58:46;;8499:77;;8447:3;8432:19;;;8585:80;8659:4;8644:20;;8620:3;8616:19;;;8637:4;8612:30;8585:80;:::i;:::-;-1:-1:-1;8721:4:46;8709:17;;8703:24;8696:4;8681:20;;8674:54;8774:4;8762:17;;;8756:24;8835:26;8818:44;;8811:4;8796:20;;;8789:74;;;;8919:22;8943:4;8915:33;8908:41;8901:49;8894:4;8879:20;;8872:79;8982:3;8967:19;8960:35;8236:765;:::o;9349:217::-;9387:3;9415:26;9476:2;9469:5;9465:14;9503:2;9494:7;9491:15;9488:41;;;9509:18;;:::i;:::-;9558:1;9545:15;;9349:217;-1:-1:-1;;;9349:217:46:o;9571:316::-;9781:3;9766:19;;9794:43;9770:9;9819:6;9794:43;:::i;:::-;9874:6;9868:3;9857:9;9853:19;9846:35;9571:316;;;;;:::o
Metadata Hash
b349c83422edf55ffc91311b91bf101598d74a166e0cb39249e9feb3245b39db
Age | Block | Fee Address | BC Fee Address | Voting Power | Jailed | Incoming |
---|
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.