文件 1 的 12:AssetManager.sol
pragma solidity 0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IAssetManager.sol";
import "./interfaces/ITwapOraclePriceFeed.sol";
import "./libraries/TribeOneHelper.sol";
contract AssetManager is Ownable, ReentrancyGuard, IAssetManager {
event AddAvailableLoanAsset(address _sender, address _asset);
event SetLoanTwapOracle(address _asset, address _twap);
event RemoveAvailableLoanAsset(address _sender, address _asset);
event AddAvailableCollateralAsset(address _sender, address _asset);
event RemoveAvailableCollateralAsset(address _sender, address _asset);
event SetConsumer(address _setter, address _consumer);
event SetAutomaticLoanLimit(address _setter, uint256 _oldLimit, uint256 _newLimit);
event TransferAsset(address indexed _requester, address _to, address _token, uint256 _amount);
event WithdrawAsset(address indexed _to, address _token, uint256 _amount);
event SetTwapOracle(address indexed _asset, address _twap, address _user);
mapping(address => bool) private availableLoanAsset;
mapping(address => bool) private availableCollateralAsset;
address private _consumer;
uint256 public automaticLoanLimit = 200;
address public immutable WETH;
address public immutable USDC;
mapping(address => address) private twapOracles;
constructor(address _WETH, address _USDC) {
require(_WETH != address(0) && _USDC != address(0), "AssetManager: ZERO address");
availableCollateralAsset[address(0)] = true;
availableLoanAsset[address(0)] = true;
WETH = _WETH;
USDC = _USDC;
}
receive() external payable {}
modifier onlyConsumer() {
require(msg.sender == _consumer, "Not consumer");
_;
}
function consumer() external view returns (address) {
return _consumer;
}
function priceOracle(address asset) external view returns (address) {
return twapOracles[asset];
}
function addAvailableLoanAsset(address _asset) external onlyOwner nonReentrant {
require(!availableLoanAsset[_asset], "Already available");
availableLoanAsset[_asset] = true;
emit AddAvailableLoanAsset(msg.sender, _asset);
}
function removeAvailableLoanAsset(address _asset) external onlyOwner nonReentrant {
require(availableLoanAsset[_asset], "Already removed");
availableLoanAsset[_asset] = false;
emit RemoveAvailableLoanAsset(msg.sender, _asset);
}
function addAvailableCollateralAsset(address _asset) external onlyOwner nonReentrant {
require(!availableCollateralAsset[_asset], "Already available");
availableCollateralAsset[_asset] = true;
emit AddAvailableCollateralAsset(msg.sender, _asset);
}
function removeAvailableCollateralAsset(address _asset) external onlyOwner nonReentrant {
require(availableCollateralAsset[_asset], "Already removed");
availableCollateralAsset[_asset] = false;
emit RemoveAvailableCollateralAsset(msg.sender, _asset);
}
function isAvailableLoanAsset(address _asset) external view override returns (bool) {
return availableLoanAsset[_asset];
}
function isAvailableCollateralAsset(address _asset) external view override returns (bool) {
return availableCollateralAsset[_asset];
}
function setConsumer(address _consumer_) external onlyOwner {
require(_consumer_ != _consumer, "Already set as consumer");
require(_consumer_ != address(0), "ZERO_ADDRESS");
_consumer = _consumer_;
emit SetConsumer(msg.sender, _consumer_);
}
function setLoanAssetTwapOracle(address _asset, address _twap) external onlyOwner nonReentrant {
require(availableLoanAsset[_asset], "AssetManager: Invalid loan asset");
address token0 = ITwapOraclePriceFeed(_twap).token0();
address token1 = ITwapOraclePriceFeed(_twap).token1();
if (_asset == address(0)) {
require((token0 == WETH && token1 == USDC) || (token0 == USDC && token1 == WETH), "AssetManager: Invalid twap");
} else {
require((token0 == _asset && token1 == USDC) || (token0 == USDC && token1 == _asset), "AssetManager: Invalid twap");
}
twapOracles[_asset] = _twap;
emit SetTwapOracle(_asset, _twap, msg.sender);
}
function setAutomaticLoanLimit(uint256 _newLimit) external onlyOwner {
require(automaticLoanLimit != _newLimit, "AssetManager: New value is same as old");
uint256 oldLimit = automaticLoanLimit;
automaticLoanLimit = _newLimit;
emit SetAutomaticLoanLimit(msg.sender, oldLimit, _newLimit);
}
function isValidAutomaticLoan(address _asset, uint256 _amountIn) external view override returns (bool) {
require(availableLoanAsset[_asset], "AssetManager: Invalid loan asset");
uint256 usdcAmount;
if (_asset == USDC) {
usdcAmount = _amountIn;
} else {
address _twap = twapOracles[_asset];
require(_twap != address(0), "AssetManager: Twap oracle was not set");
if (_asset == address(0)) {
_asset = WETH;
}
usdcAmount = ITwapOraclePriceFeed(_twap).consult(_asset, _amountIn);
}
return usdcAmount <= automaticLoanLimit * (10**IERC20Metadata(USDC).decimals());
}
function requestETH(address _to, uint256 _amount) external override onlyConsumer {
require(address(this).balance >= _amount, "Asset Manager: Insufficient balance");
TribeOneHelper.safeTransferETH(_to, _amount);
emit TransferAsset(msg.sender, _to, address(0), _amount);
}
function requestToken(
address _to,
address _token,
uint256 _amount
) external override onlyConsumer {
require(IERC20(_token).balanceOf(address(this)) >= _amount, "Asset Manager: Insufficient balance");
TribeOneHelper.safeTransfer(_token, _to, _amount);
emit TransferAsset(msg.sender, _to, _token, _amount);
}
function withdrawAsset(
address _to,
address _token,
uint256 _amount
) external onlyOwner {
require(_to != address(0), "ZERO Address");
if (_token == address(0)) {
_amount = address(this).balance;
TribeOneHelper.safeTransferETH(msg.sender, _amount);
} else {
TribeOneHelper.safeTransfer(_token, msg.sender, _amount);
}
emit WithdrawAsset(_to, _token, _amount);
}
function collectInstallment(
address _currency,
uint256 _amount,
uint256 _interest,
bool _collateral
) external payable override onlyConsumer {
if (_currency == address(0)) {
require(msg.value == _amount, "Wrong msg.value");
} else {
TribeOneHelper.safeTransferFrom(_currency, msg.sender, address(this), _amount);
}
}
}
文件 2 的 12:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
this;
return msg.data;
}
}
文件 3 的 12:IAssetManager.sol
pragma solidity 0.8.0;
interface IAssetManager {
function isAvailableLoanAsset(address _asset) external returns (bool);
function isAvailableCollateralAsset(address _asset) external returns (bool);
function isValidAutomaticLoan(address _asset, uint256 _amountIn) external returns (bool);
function requestETH(address _to, uint256 _amount) external;
function requestToken(
address _to,
address _token,
uint256 _amount
) external;
function collectInstallment(
address _currency,
uint256 _amount,
uint256 _interest,
bool _collateral
) external payable;
}
文件 4 的 12:IERC1155.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC1155 is IERC165 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}
文件 5 的 12:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 6 的 12:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 7 的 12:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 8 的 12:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}
文件 9 的 12:ITwapOraclePriceFeed.sol
pragma solidity 0.8.0;
interface ITwapOraclePriceFeed {
function token0() external returns (address);
function token1() external returns (address);
function consult(address token, uint256 amountIn) external view returns (uint256 amountOut);
}
文件 10 的 12:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor () {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
文件 11 的 12:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
文件 12 的 12:TribeOneHelper.sol
pragma solidity 0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
library TribeOneHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper::safeApprove: approve failed");
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TribeOneHelper::safeTransfer: transfer failed");
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TribeOneHelper::transferFrom: transferFrom failed");
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, "TribeOneHelper::safeTransferETH: ETH transfer failed");
}
function safeTransferAsset(
address token,
address to,
uint256 value
) internal {
if (token == address(0)) {
safeTransferETH(to, value);
} else {
safeTransfer(token, to, value);
}
}
function safeNFTApproveForAll(
address nft,
address operator,
bool approved
) internal {
(bool success, ) = nft.call(abi.encodeWithSelector(0xa22cb465, operator, approved));
require(success, "TribeOneHelper::safeNFTApproveForAll: Failed");
}
function safeTransferNFT(
address _nft,
address _from,
address _to,
bool isERC721,
uint256 _tokenId
) internal {
if (isERC721) {
IERC721(_nft).safeTransferFrom(_from, _to, _tokenId);
} else {
IERC1155(_nft).safeTransferFrom(_from, _to, _tokenId, 1, "0x00");
}
}
function getExpectedPrice(
uint256 _fundAmount,
uint256 _percentage,
uint256 _slippage
) internal pure returns (uint256) {
require(_percentage != 0, "TribeOneHelper: percentage should not be 0");
return (_fundAmount * (10000 + _slippage)) / _percentage;
}
}
{
"compilationTarget": {
"contracts/AssetManager.sol": "AssetManager"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_WETH","type":"address"},{"internalType":"address","name":"_USDC","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"address","name":"_asset","type":"address"}],"name":"AddAvailableCollateralAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"address","name":"_asset","type":"address"}],"name":"AddAvailableLoanAsset","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":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"address","name":"_asset","type":"address"}],"name":"RemoveAvailableCollateralAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sender","type":"address"},{"indexed":false,"internalType":"address","name":"_asset","type":"address"}],"name":"RemoveAvailableLoanAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_setter","type":"address"},{"indexed":false,"internalType":"uint256","name":"_oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_newLimit","type":"uint256"}],"name":"SetAutomaticLoanLimit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_setter","type":"address"},{"indexed":false,"internalType":"address","name":"_consumer","type":"address"}],"name":"SetConsumer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_asset","type":"address"},{"indexed":false,"internalType":"address","name":"_twap","type":"address"}],"name":"SetLoanTwapOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_asset","type":"address"},{"indexed":false,"internalType":"address","name":"_twap","type":"address"},{"indexed":false,"internalType":"address","name":"_user","type":"address"}],"name":"SetTwapOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_requester","type":"address"},{"indexed":false,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"address","name":"_token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"TransferAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"address","name":"_token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"WithdrawAsset","type":"event"},{"inputs":[],"name":"USDC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"addAvailableCollateralAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"addAvailableLoanAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"automaticLoanLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_currency","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_interest","type":"uint256"},{"internalType":"bool","name":"_collateral","type":"bool"}],"name":"collectInstallment","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"consumer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"isAvailableCollateralAsset","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"isAvailableLoanAsset","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_amountIn","type":"uint256"}],"name":"isValidAutomaticLoan","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"priceOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"removeAvailableCollateralAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"}],"name":"removeAvailableLoanAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"requestETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"requestToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newLimit","type":"uint256"}],"name":"setAutomaticLoanLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_consumer_","type":"address"}],"name":"setConsumer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"address","name":"_twap","type":"address"}],"name":"setLoanAssetTwapOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]