Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/solidity-stringutils"]
path = lib/solidity-stringutils
url = https://github.com/Arachnid/solidity-stringutils
[submodule "lib/chainlink-brownie-contracts"]
path = lib/chainlink-brownie-contracts
url = https://github.com/smartcontractkit/chainlink-brownie-contracts
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![Mentioned in Awesome Foundry](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/crisgarner/awesome-foundry)

# Foundry + Hardhat Diamonds
# Foundry + Hardhat Diamonds sure

This is a mimimal template for [Diamonds](https://github.com/ethereum/EIPs/issues/2535) which allows facet selectors to be generated on the go in solidity tests!

Expand Down
108 changes: 108 additions & 0 deletions contracts/facets/BorrowerFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IBorrowerFacet} from "../interfaces/IBorrowerFacet.sol";
import {IERC721} from "../interfaces/IERC721.sol";
import {LibAppStorage} from "../libraries/LibAppStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";

contract BorrowerFacet is IBorrowerFacet {
error TokenNotOwned(uint256 tokenId);
error TokenAlreadyBorrowed(uint256 tokenId);
error TokenNotBorrowed(uint256 tokenId);
error BorrowNotExpired(uint256 tokenId);
error DurationTooLong(uint256 duration);
error InsufficientFee(uint256 sent, uint256 required);
error Unauthorized();

event TokenBorrowed(address indexed borrower, uint256 indexed tokenId, uint256 duration, uint256 fee, uint256 deadline);
event TokenReturned(address indexed borrower, uint256 indexed tokenId);
event BorrowFeeUpdated(uint256 oldFee, uint256 newFee);
event MaxBorrowDurationUpdated(uint256 oldDuration, uint256 newDuration);

function borrowToken(uint256 tokenId, uint256 duration) external payable {
LibAppStorage.NFTStorage storage nft = LibAppStorage.nftStorage();
LibAppStorage.BorrowStorage storage brw = LibAppStorage.borrowStorage();

if (nft.owners[tokenId] == address(0)) revert TokenNotOwned(tokenId);
if (nft.owners[tokenId] == msg.sender) revert TokenNotOwned(tokenId);
if (brw.borrows[tokenId].active) revert TokenAlreadyBorrowed(tokenId);
if (duration > brw.maxBorrowDuration) revert DurationTooLong(duration);

uint256 fee = brw.borrowFee;
if (msg.value < fee) revert InsufficientFee(msg.value, fee);

address originalOwner = nft.owners[tokenId];
brw.originalOwners[tokenId] = originalOwner;

nft.balances[originalOwner] -= 1;
nft.balances[msg.sender] += 1;
nft.owners[tokenId] = msg.sender;

brw.borrows[tokenId] = LibAppStorage.BorrowInfoStruct({
borrower: msg.sender,
deadline: block.timestamp + duration,
fee: fee,
active: true
});

emit TokenBorrowed(msg.sender, tokenId, duration, fee, block.timestamp + duration);
}

function returnToken(uint256 tokenId) external {
LibAppStorage.NFTStorage storage nft = LibAppStorage.nftStorage();
LibAppStorage.BorrowStorage storage brw = LibAppStorage.borrowStorage();
LibAppStorage.BorrowInfoStruct storage borrow = brw.borrows[tokenId];

if (!borrow.active) revert TokenNotBorrowed(tokenId);
if (borrow.borrower != msg.sender) revert Unauthorized();
if (block.timestamp < borrow.deadline) revert BorrowNotExpired(tokenId);

address originalOwner = brw.originalOwners[tokenId];

nft.balances[msg.sender] -= 1;
nft.balances[originalOwner] += 1;
nft.owners[tokenId] = originalOwner;

delete brw.borrows[tokenId];
delete brw.originalOwners[tokenId];

emit TokenReturned(msg.sender, tokenId);
}

function getBorrowInfo(uint256 tokenId) external view returns (
address borrower,
uint256 deadline,
uint256 fee,
bool active
) {
LibAppStorage.BorrowInfoStruct storage b = LibAppStorage.borrowStorage().borrows[tokenId];
return (b.borrower, b.deadline, b.fee, b.active);
}

function isBorrowed(uint256 tokenId) external view returns (bool) {
return LibAppStorage.borrowStorage().borrows[tokenId].active;
}

function borrowFee() external view returns (uint256) {
return LibAppStorage.borrowStorage().borrowFee;
}

function maxBorrowDuration() external view returns (uint256) {
return LibAppStorage.borrowStorage().maxBorrowDuration;
}

function setBorrowFee(uint256 _fee) external {
LibDiamond.enforceIsContractOwner();
uint256 oldFee = LibAppStorage.borrowStorage().borrowFee;
LibAppStorage.borrowStorage().borrowFee = _fee;
emit BorrowFeeUpdated(oldFee, _fee);
}

function setMaxBorrowDuration(uint256 _duration) external {
LibDiamond.enforceIsContractOwner();
uint256 oldDuration = LibAppStorage.borrowStorage().maxBorrowDuration;
LibAppStorage.borrowStorage().maxBorrowDuration = _duration;
emit MaxBorrowDurationUpdated(oldDuration, _duration);
}
}
15 changes: 2 additions & 13 deletions contracts/facets/DiamondCutFacet.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/******************************************************************************\
* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
/******************************************************************************/

import { IDiamondCut } from "../interfaces/IDiamondCut.sol";
import { LibDiamond } from "../libraries/LibDiamond.sol";
import {IDiamondCut} from "../interfaces/IDiamondCut.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";

contract DiamondCutFacet is IDiamondCut {
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
Expand Down
121 changes: 121 additions & 0 deletions contracts/facets/ERC20Facet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20, IERC20Metadata} from "../interfaces/IERC20.sol";
import {LibAppStorage} from "../libraries/LibAppStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";

contract ERC20Facet is IERC20, IERC20Metadata {
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
error ERC20InvalidSender(address sender);
error ERC20InvalidReceiver(address receiver);
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
error ERC20InvalidSpender(address spender);
error ERC20ExceedsMaxSupply(uint256 amount, uint256 maxSupply);

function totalSupplyERC20() external view returns (uint256) {
return LibAppStorage.erc20Storage().erc20TotalSupply;
}

function balanceOfERC20(address account) external view returns (uint256) {
return LibAppStorage.erc20Storage().erc20Balances[account];
}

function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) external view returns (uint256) {
return LibAppStorage.erc20Storage().erc20Allowances[owner][spender];
}

function approveERC20(address spender, uint256 amount) external returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFromERC20(address from, address to, uint256 amount) external returns (bool) {
_spendAllowance(from, msg.sender, amount);
_transfer(from, to, amount);
return true;
}

function nameERC20() external view returns (string memory) {
return LibAppStorage.erc20Storage().erc20Name;
}

function symbolERC20() external view returns (string memory) {
return LibAppStorage.erc20Storage().erc20Symbol;
}

function decimalsERC20() external view returns (uint8) {
return LibAppStorage.erc20Storage().erc20Decimals;
}

function mintERC20(address to, uint256 amount) external {
LibDiamond.enforceIsContractOwner();
if (to == address(0)) revert ERC20InvalidReceiver(address(0));

LibAppStorage.ERC20Storage storage s = LibAppStorage.erc20Storage();
if (s.erc20TotalSupply + amount > s.erc20MaxSupply)
revert ERC20ExceedsMaxSupply(amount, s.erc20MaxSupply);

s.erc20Balances[to] += amount;
s.erc20TotalSupply += amount;
emit Transfer(address(0), to, amount);
}

function burnERC20(address from, uint256 amount) external {
LibDiamond.enforceIsContractOwner();
_burn(from, amount);
}

function burnERC20() external {
_burn(msg.sender, LibAppStorage.erc20Storage().erc20Balances[msg.sender]);
}

function _transfer(address from, address to, uint256 amount) internal {
if (from == address(0)) revert ERC20InvalidSender(address(0));
if (to == address(0)) revert ERC20InvalidReceiver(address(0));

LibAppStorage.ERC20Storage storage s = LibAppStorage.erc20Storage();
uint256 fromBalance = s.erc20Balances[from];
if (fromBalance < amount)
revert ERC20InsufficientBalance(from, fromBalance, amount);

s.erc20Balances[from] = fromBalance - amount;
s.erc20Balances[to] += amount;
emit Transfer(from, to, amount);
}

function _approve(address owner, address spender, uint256 amount) internal {
if (owner == address(0)) revert ERC20InvalidSender(address(0));
if (spender == address(0)) revert ERC20InvalidSpender(address(0));

LibAppStorage.erc20Storage().erc20Allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}

function _spendAllowance(address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = LibAppStorage.erc20Storage().erc20Allowances[owner][spender];
if (currentAllowance != type(uint256).max) {
if (currentAllowance < amount)
revert ERC20InsufficientAllowance(spender, currentAllowance, amount);
LibAppStorage.erc20Storage().erc20Allowances[owner][spender] = currentAllowance - amount;
}
}

function _burn(address from, uint256 amount) internal {
if (from == address(0)) revert ERC20InvalidSender(address(0));

LibAppStorage.ERC20Storage storage s = LibAppStorage.erc20Storage();
uint256 fromBalance = s.erc20Balances[from];
if (fromBalance < amount)
revert ERC20InsufficientBalance(from, fromBalance, amount);

s.erc20Balances[from] = fromBalance - amount;
s.erc20TotalSupply -= amount;
emit Transfer(from, address(0), amount);
}
}
Loading