-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Builder #157
Open
a17
wants to merge
3
commits into
main
Choose a base branch
from
156-builder-profile-nft
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Builder #157
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
//import {console} from "forge-std/Test.sol"; | ||
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import {Controllable} from "./base/Controllable.sol"; | ||
import {IControllable} from "../interfaces/IControllable.sol"; | ||
import {IBuilder} from "../interfaces/IBuilder.sol"; | ||
import {IPriceReader} from "../interfaces/IPriceReader.sol"; | ||
import {IPlatform} from "../interfaces/IPlatform.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; | ||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
/// @title Decentralized platform builder | ||
/// @author Alien Deployer (https://github.com/a17) | ||
contract Builder is Controllable, ERC721Upgradeable, ReentrancyGuardUpgradeable, IBuilder { | ||
using SafeERC20 for IERC20; | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* CONSTANTS */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @notice Version of Builder contract implementation | ||
string public constant VERSION = "1.0.0"; | ||
|
||
/// @notice Contract uses 2 decimals to represent dollar amount | ||
uint48 public constant USD_DENOMINATOR = 1_00; | ||
|
||
/// @notice Minimum USD amount for the first investment by the user | ||
uint48 public constant MIN_AMOUNT_FOR_MINTING = 10_00; | ||
|
||
/// @notice Minimum USD amount for investment by existing user | ||
uint48 public constant MIN_AMOUNT_FOR_ADDING = 1_00; | ||
|
||
// keccak256(abi.encode(uint256(keccak256("erc7201:stability.Builder")) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 private constant BUILDER_STORAGE_LOCATION = | ||
0xb43e9b39f8b177b6f23ef5d977f7f3bce46e298c7c0f146b78fa5d5641b83f00; | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* STORAGE */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @custom:storage-location erc7201:stability.Builder | ||
struct BuilderStorage { | ||
uint newTokenId; | ||
mapping(address owner => uint tokenId) ownedToken; | ||
mapping(uint tokenId => TokenData) tokenData; | ||
} | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* INITIALIZATION */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
function initialize(address platform_) external initializer { | ||
__Controllable_init(platform_); | ||
__ReentrancyGuard_init(); | ||
__ERC721_init("Stability Builder", "BUILDER"); | ||
BuilderStorage storage $ = _getStorage(); | ||
$.newTokenId = 1; | ||
} | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* WRITE FUNCTIONS */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @inheritdoc IBuilder | ||
function invest(address asset, uint amount) external nonReentrant returns (uint48 amountUSD) { | ||
IPriceReader priceReader = IPriceReader(IPlatform(platform()).priceReader()); | ||
(uint price, bool trusted) = priceReader.getPrice(asset); | ||
if (!trusted) { | ||
revert NotTrustedPriceFor(asset); | ||
} | ||
amountUSD = uint48(amount * price / 10 ** IERC20Metadata(asset).decimals() * USD_DENOMINATOR / 1e18); | ||
if (amountUSD == 0) { | ||
revert ZeroAmountToInvest(); | ||
} | ||
|
||
BuilderStorage storage $ = _getStorage(); | ||
uint tokenId = $.ownedToken[msg.sender]; | ||
|
||
if (tokenId == 0) { | ||
if (amountUSD < MIN_AMOUNT_FOR_MINTING) { | ||
revert TooLittleAmountToInvest(amountUSD, MIN_AMOUNT_FOR_MINTING); | ||
} | ||
tokenId = $.newTokenId; | ||
$.newTokenId = tokenId + 1; | ||
$.ownedToken[msg.sender] = tokenId; | ||
_mint(msg.sender, tokenId); | ||
} else { | ||
if (amountUSD < MIN_AMOUNT_FOR_ADDING) { | ||
revert TooLittleAmountToInvest(amountUSD, MIN_AMOUNT_FOR_ADDING); | ||
} | ||
} | ||
|
||
TokenData storage tokenData = $.tokenData[tokenId]; | ||
tokenData.invested += amountUSD; | ||
|
||
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); | ||
|
||
emit Invested(msg.sender, tokenId, asset, amount, amountUSD); | ||
} | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* VIEW FUNCTIONS */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @inheritdoc Controllable | ||
function supportsInterface(bytes4 interfaceId) | ||
public | ||
view | ||
virtual | ||
override(ERC721Upgradeable, Controllable) | ||
returns (bool) | ||
{ | ||
return interfaceId == type(IControllable).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* INTERNAL LOGIC */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
function _getStorage() private pure returns (BuilderStorage storage $) { | ||
//slither-disable-next-line assembly | ||
assembly { | ||
$.slot := BUILDER_STORAGE_LOCATION | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
interface IBuilder { | ||
event Invested(address indexed user, uint indexed tokenId, address indexed asset, uint amount, uint48 amountUSD); | ||
|
||
error NotTrustedPriceFor(address asset); | ||
error ZeroAmountToInvest(); | ||
error TooLittleAmountToInvest(uint amountUSD, uint minimum); | ||
|
||
struct TokenData { | ||
uint8 role; | ||
uint48 invested; | ||
uint48 bountyPending; | ||
uint48 bountyGot; | ||
} | ||
|
||
/// @notice Invest money in development | ||
/// @param asset Allowed asset | ||
/// @param amount Amount of asset | ||
/// @return amountUSD Invested USD amount with 2 decimals | ||
function invest(address asset, uint amount) external returns (uint48 amountUSD); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
import {Test, console, Vm} from "forge-std/Test.sol"; | ||
import {FullMockSetup} from "../base/FullMockSetup.sol"; | ||
import {Builder} from "../../src/core/Builder.sol"; | ||
import {Proxy} from "../../src/core/proxy/Proxy.sol"; | ||
import {IControllable} from "../../src/interfaces/IControllable.sol"; | ||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | ||
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; | ||
|
||
contract BuilderTest is Test, FullMockSetup { | ||
Builder public builder; | ||
|
||
function setUp() public { | ||
Proxy proxy = new Proxy(); | ||
address implementation = address(new Builder()); | ||
proxy.initProxy(implementation); | ||
builder = Builder(address(proxy)); | ||
builder.initialize(address(platform)); | ||
|
||
tokenA.mint(25e18); | ||
tokenA.approve(address(builder), 25e18); | ||
} | ||
|
||
function testInvest() public { | ||
builder.invest(address(tokenA), 11e18); | ||
} | ||
|
||
function testERC165() public { | ||
assertEq(builder.supportsInterface(type(IERC165).interfaceId), true); | ||
assertEq(builder.supportsInterface(type(IERC721).interfaceId), true); | ||
assertEq(builder.supportsInterface(type(IERC721Metadata).interfaceId), true); | ||
assertEq(builder.supportsInterface(type(IControllable).interfaceId), true); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / Slither
Divide before multiply Medium