文件 1 的 10:CrowdfundWithPodiumEditionsFactory.sol
pragma solidity 0.8.6;
import {CrowdfundWithPodiumEditionsProxy} from "./CrowdfundWithPodiumEditionsProxy.sol";
import {CrowdfundWithPodiumEditionsLogic} from "./CrowdfundWithPodiumEditionsLogic.sol";
import {ICrowdfundWithPodiumEditions} from "./interface/ICrowdfundWithPodiumEditions.sol";
import {ITributaryRegistry} from "../../../interface/ITributaryRegistry.sol";
import {Governable} from "../../../lib/Governable.sol";
contract CrowdfundWithPodiumEditionsFactory is Governable {
struct Parameters {
address payable fundingRecipient;
uint256 fundingCap;
uint256 operatorPercent;
string name;
string symbol;
uint256 feePercentage;
uint256 podiumDuration;
}
event CrowdfundDeployed(
address crowdfundProxy,
string name,
string symbol,
address operator
);
address public logic;
address payable public editions;
address public tributaryRegistry;
address public treasuryConfig;
uint256 public minFeePercentage = 250;
Parameters public parameters;
constructor(
address owner_,
address logic_,
address payable editions_,
address tributaryRegistry_,
address treasuryConfig_
) Governable(owner_) {
logic = logic_;
editions = editions_;
tributaryRegistry = tributaryRegistry_;
treasuryConfig = treasuryConfig_;
}
function setMinimumFeePercentage(uint256 newMinFeePercentage)
public
onlyGovernance
{
minFeePercentage = newMinFeePercentage;
}
function setEditions(address payable newEditions) public onlyGovernance {
editions = newEditions;
}
function setLogic(address newLogic) public onlyGovernance {
logic = newLogic;
}
function setTreasuryConfig(address newTreasuryConfig)
public
onlyGovernance
{
treasuryConfig = newTreasuryConfig;
}
function setTributaryRegistry(address newTributaryRegistry)
public
onlyGovernance
{
tributaryRegistry = newTributaryRegistry;
}
struct TributaryConfig {
address tributary;
uint256 feePercentage;
}
function createCrowdfund(
ICrowdfundWithPodiumEditions.EditionTier[] calldata tiers,
TributaryConfig calldata tributaryConfig,
string calldata name_,
string calldata symbol_,
address payable operator_,
address payable fundingRecipient_,
uint256 fundingCap_,
uint256 operatorPercent_,
uint256 podiumDuration_
) external returns (address crowdfundProxy) {
require(
tributaryConfig.feePercentage >= minFeePercentage,
"fee is too low"
);
parameters = Parameters({
name: name_,
symbol: symbol_,
fundingRecipient: fundingRecipient_,
fundingCap: fundingCap_,
operatorPercent: operatorPercent_,
feePercentage: tributaryConfig.feePercentage,
podiumDuration: podiumDuration_
});
crowdfundProxy = address(
new CrowdfundWithPodiumEditionsProxy{
salt: keccak256(abi.encode(symbol_, operator_))
}(treasuryConfig, operator_)
);
delete parameters;
emit CrowdfundDeployed(crowdfundProxy, name_, symbol_, operator_);
ITributaryRegistry(tributaryRegistry).registerTributary(
crowdfundProxy,
tributaryConfig.tributary
);
ICrowdfundWithPodiumEditions(editions).createEditions(
tiers,
payable(crowdfundProxy),
crowdfundProxy
);
}
}
文件 2 的 10:CrowdfundWithPodiumEditionsLogic.sol
pragma solidity 0.8.6;
import {CrowdfundWithPodiumEditionsStorage} from "./CrowdfundWithPodiumEditionsStorage.sol";
import {ICrowdfundWithPodiumEditions} from "./interface/ICrowdfundWithPodiumEditions.sol";
import {ITreasuryConfig} from "../../../interface/ITreasuryConfig.sol";
contract CrowdfundWithPodiumEditionsLogic is
CrowdfundWithPodiumEditionsStorage
{
event ReceivedERC721(uint256 tokenId, address sender);
event Contribution(address contributor, uint256 amount);
event ContributionForEdition(
address contributor,
uint256 amount,
uint256 editionId,
uint256 tokenId
);
event FundingClosed(uint256 amountRaised, uint256 creatorAllocation);
event BidAccepted(uint256 amount);
event Redeemed(address contributor, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event PodiumDurationExtended(uint256 editionId);
modifier onlyOperator() {
require(msg.sender == operator);
_;
}
modifier nonReentrant() {
require(reentrancy_status != REENTRANCY_ENTERED, "Reentrant call");
reentrancy_status = REENTRANCY_ENTERED;
_;
reentrancy_status = REENTRANCY_NOT_ENTERED;
}
function contributeForPodium(
address payable backer,
uint256 editionId,
uint256 amount
) external payable nonReentrant {
_contribute(backer, editionId, amount, true);
}
function contribute(
address payable backer,
uint256 editionId,
uint256 amount
) external payable nonReentrant {
_contribute(backer, editionId, amount, false);
}
function redeem(uint256 tokenAmount) external nonReentrant {
require(
address(this).balance > 0,
"Crowdfund: No ETH available to redeem"
);
require(
balanceOf[msg.sender] >= tokenAmount,
"Crowdfund: Insufficient balance"
);
require(status == Status.TRADING, "Crowdfund: Funding must be trading");
uint256 redeemable = redeemableFromTokens(tokenAmount);
_burn(msg.sender, tokenAmount);
sendValue(payable(msg.sender), redeemable);
emit Redeemed(msg.sender, redeemable);
}
function redeemableFromTokens(uint256 tokenAmount)
public
view
returns (uint256)
{
return (tokenAmount * address(this).balance) / totalSupply;
}
function valueToTokens(uint256 value) public pure returns (uint256 tokens) {
tokens = value * TOKEN_SCALE;
}
function tokensToValue(uint256 tokenAmount)
internal
pure
returns (uint256 value)
{
value = tokenAmount / TOKEN_SCALE;
}
function closeFunding() external onlyOperator nonReentrant {
require(status == Status.FUNDING, "Crowdfund: Funding must be open");
status = Status.TRADING;
uint256 operatorTokens = (operatorPercent * totalSupply) /
(100 - operatorPercent);
_mint(operator, operatorTokens);
emit FundingClosed(address(this).balance, operatorTokens);
sendValue(
ITreasuryConfig(treasuryConfig).treasury(),
computeFee(address(this).balance)
);
sendValue(fundingRecipient, address(this).balance);
}
function computeFee(uint256 amount) public view returns (uint256 fee) {
fee = (feePercentage * amount) / (100 * 100);
}
function sendValue(address payable recipient, uint256 amount) internal {
require(
address(this).balance >= amount,
"Address: insufficient balance"
);
(bool success, ) = recipient.call{value: amount}("");
require(
success,
"Address: unable to send value, recipient may have reverted"
);
}
function _mint(address to, uint256 value) internal {
totalSupply = totalSupply + value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(address(0), to, value);
}
function _burn(address from, uint256 value) internal {
balanceOf[from] = balanceOf[from] - value;
totalSupply = totalSupply - value;
emit Transfer(from, address(0), value);
}
function _approve(
address owner,
address spender,
uint256 value
) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(
address from,
address to,
uint256 value
) private {
balanceOf[from] = balanceOf[from] - value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(from, to, value);
}
function approve(address spender, uint256 value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool) {
allowance[from][msg.sender] = allowance[from][msg.sender] - value;
_transfer(from, to, value);
return true;
}
function buyEdition(
uint256 amount,
uint256 editionId,
address recipient
) internal returns (uint256) {
require(
amount >=
ICrowdfundWithPodiumEditions(editions).editionPrice(editionId),
"Unable purchase edition with available amount"
);
return
ICrowdfundWithPodiumEditions(editions).buyEdition(
editionId,
recipient
);
}
function buyEditionForPodium(
uint256 amount,
uint256 editionId,
address recipient
) internal returns (uint256) {
require(
amount >=
ICrowdfundWithPodiumEditions(editions).editionPrice(editionId),
"Unable purchase edition with available amount"
);
if (podiumStartTime == 0) {
podiumStartTime = block.timestamp;
}
uint256 podiumEnds = podiumStartTime + podiumDuration;
require(podiumEnds >= block.timestamp, "podium closed");
if (podiumEnds < block.timestamp + PODIUM_TIME_BUFFER) {
podiumDuration += block.timestamp + PODIUM_TIME_BUFFER - podiumEnds;
emit PodiumDurationExtended(editionId);
}
return
ICrowdfundWithPodiumEditions(editions).buyEdition(
editionId,
recipient
);
}
function _contribute(
address payable backer,
uint256 editionId,
uint256 amount,
bool forPodium
) private {
require(status == Status.FUNDING, "Crowdfund: Funding must be open");
require(amount == msg.value, "Crowdfund: Amount is not value sent");
if (address(this).balance <= fundingCap) {
_mint(backer, valueToTokens(amount));
if (editionId > 0) {
emit ContributionForEdition(
backer,
amount,
editionId,
forPodium
? buyEditionForPodium(amount, editionId, backer)
: buyEdition(amount, editionId, backer)
);
} else {
emit Contribution(backer, amount);
}
} else {
uint256 startAmount = address(this).balance - amount;
require(
startAmount < fundingCap,
"Crowdfund: Funding cap already reached"
);
uint256 eligibleAmount = fundingCap - startAmount;
_mint(backer, valueToTokens(eligibleAmount));
if (editionId > 0) {
emit ContributionForEdition(
backer,
eligibleAmount,
editionId,
forPodium
? buyEditionForPodium(eligibleAmount, editionId, backer)
: buyEdition(eligibleAmount, editionId, backer)
);
} else {
emit Contribution(backer, eligibleAmount);
}
sendValue(backer, amount - eligibleAmount);
}
}
}
文件 3 的 10:CrowdfundWithPodiumEditionsProxy.sol
pragma solidity 0.8.6;
import {CrowdfundWithPodiumEditionsStorage} from "./CrowdfundWithPodiumEditionsStorage.sol";
interface ICrowdfundWithPodiumEditionsFactory {
function mediaAddress() external returns (address);
function logic() external returns (address);
function editions() external returns (address);
function parameters()
external
returns (
address payable fundingRecipient,
uint256 fundingCap,
uint256 operatorPercent,
string memory name,
string memory symbol,
uint256 feePercentage,
uint256 podiumDuration
);
}
contract CrowdfundWithPodiumEditionsProxy is
CrowdfundWithPodiumEditionsStorage
{
constructor(address treasuryConfig_, address payable operator_) {
logic = ICrowdfundWithPodiumEditionsFactory(msg.sender).logic();
editions = ICrowdfundWithPodiumEditionsFactory(msg.sender).editions();
(
fundingRecipient,
fundingCap,
operatorPercent,
name,
symbol,
feePercentage,
podiumDuration
) = ICrowdfundWithPodiumEditionsFactory(msg.sender).parameters();
operator = operator_;
treasuryConfig = treasuryConfig_;
status = Status.FUNDING;
}
fallback() external payable {
address _impl = logic;
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
receive() external payable {}
}
文件 4 的 10:CrowdfundWithPodiumEditionsStorage.sol
pragma solidity 0.8.6;
contract CrowdfundWithPodiumEditionsStorage {
enum Status {
FUNDING,
TRADING
}
uint16 internal constant TOKEN_SCALE = 1000;
uint256 internal constant REENTRANCY_NOT_ENTERED = 1;
uint256 internal constant REENTRANCY_ENTERED = 2;
uint16 public constant PODIUM_TIME_BUFFER = 900;
uint8 public constant decimals = 18;
address payable public operator;
address payable public fundingRecipient;
address public treasuryConfig;
uint256 public fundingCap;
uint256 public feePercentage;
uint256 public operatorPercent;
string public symbol;
string public name;
Status public status;
uint256 internal reentrancy_status;
uint256 public podiumStartTime;
uint256 public podiumDuration;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
mapping(address => uint256) public nonces;
address public logic;
address public editions;
}
文件 5 的 10:Governable.sol
pragma solidity 0.8.6;
import {Ownable} from "../lib/Ownable.sol";
import {IGovernable} from "../lib/interface/IGovernable.sol";
contract Governable is Ownable, IGovernable {
address public override governor;
modifier onlyGovernance() {
require(isOwner() || isGovernor(), "caller is not governance");
_;
}
modifier onlyGovernor() {
require(isGovernor(), "caller is not governor");
_;
}
constructor(address owner_) Ownable(owner_) {}
function changeGovernor(address governor_) public override onlyGovernance {
governor = governor_;
}
function isGovernor() public view override returns (bool) {
return msg.sender == governor;
}
}
文件 6 的 10:ICrowdfundWithPodiumEditions.sol
pragma solidity 0.8.6;
interface ICrowdfundWithPodiumEditions {
struct Edition {
uint256 quantity;
uint256 price;
address payable fundingRecipient;
uint256 numSold;
bytes32 contentHash;
}
struct EditionTier {
uint256 quantity;
uint256 price;
bytes32 contentHash;
}
function buyEdition(uint256 editionId, address recipient)
external
payable
returns (uint256 tokenId);
function editionPrice(uint256 editionId) external view returns (uint256);
function createEditions(
EditionTier[] memory tier,
address payable fundingRecipient,
address minter
) external;
function contractURI() external view returns (string memory);
}
文件 7 的 10:IGovernable.sol
pragma solidity 0.8.6;
interface IGovernable {
function changeGovernor(address governor_) external;
function isGovernor() external view returns (bool);
function governor() external view returns (address);
}
文件 8 的 10:ITreasuryConfig.sol
pragma solidity 0.8.6;
interface ITreasuryConfig {
function treasury() external returns (address payable);
function distributionModel() external returns (address);
}
文件 9 的 10:ITributaryRegistry.sol
pragma solidity 0.8.6;
interface ITributaryRegistry {
function addRegistrar(address registrar) external;
function removeRegistrar(address registrar) external;
function addSingletonProducer(address producer) external;
function removeSingletonProducer(address producer) external;
function registerTributary(address producer, address tributary) external;
function producerToTributary(address producer)
external
returns (address tributary);
function singletonProducer(address producer) external returns (bool);
function changeTributary(address producer, address newTributary) external;
}
文件 10 的 10:Ownable.sol
pragma solidity 0.8.6;
contract Ownable {
address public owner;
address private nextOwner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
modifier onlyOwner() {
require(isOwner(), "caller is not the owner.");
_;
}
modifier onlyNextOwner() {
require(isNextOwner(), "current owner must set caller as next owner.");
_;
}
constructor(address owner_) {
owner = owner_;
emit OwnershipTransferred(address(0), owner);
}
function transferOwnership(address nextOwner_) external onlyOwner {
require(nextOwner_ != address(0), "Next owner is the zero address.");
nextOwner = nextOwner_;
}
function cancelOwnershipTransfer() external onlyOwner {
delete nextOwner;
}
function acceptOwnership() external onlyNextOwner {
delete nextOwner;
owner = msg.sender;
emit OwnershipTransferred(owner, msg.sender);
}
function renounceOwnership() external onlyOwner {
owner = address(0);
emit OwnershipTransferred(owner, address(0));
}
function isOwner() public view returns (bool) {
return msg.sender == owner;
}
function isNextOwner() public view returns (bool) {
return msg.sender == nextOwner;
}
}
{
"compilationTarget": {
"contracts/producers/crowdfunds/crowdfund-with-podium-editions/CrowdfundWithPodiumEditionsFactory.sol": "CrowdfundWithPodiumEditionsFactory"
},
"evmVersion": "berlin",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 2000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"logic_","type":"address"},{"internalType":"address payable","name":"editions_","type":"address"},{"internalType":"address","name":"tributaryRegistry_","type":"address"},{"internalType":"address","name":"treasuryConfig_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"crowdfundProxy","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"string","name":"symbol","type":"string"},{"indexed":false,"internalType":"address","name":"operator","type":"address"}],"name":"CrowdfundDeployed","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"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelOwnershipTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"governor_","type":"address"}],"name":"changeGovernor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"bytes32","name":"contentHash","type":"bytes32"}],"internalType":"struct ICrowdfundWithPodiumEditions.EditionTier[]","name":"tiers","type":"tuple[]"},{"components":[{"internalType":"address","name":"tributary","type":"address"},{"internalType":"uint256","name":"feePercentage","type":"uint256"}],"internalType":"struct CrowdfundWithPodiumEditionsFactory.TributaryConfig","name":"tributaryConfig","type":"tuple"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address payable","name":"operator_","type":"address"},{"internalType":"address payable","name":"fundingRecipient_","type":"address"},{"internalType":"uint256","name":"fundingCap_","type":"uint256"},{"internalType":"uint256","name":"operatorPercent_","type":"uint256"},{"internalType":"uint256","name":"podiumDuration_","type":"uint256"}],"name":"createCrowdfund","outputs":[{"internalType":"address","name":"crowdfundProxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"editions","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isGovernor","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isNextOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"logic","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parameters","outputs":[{"internalType":"address payable","name":"fundingRecipient","type":"address"},{"internalType":"uint256","name":"fundingCap","type":"uint256"},{"internalType":"uint256","name":"operatorPercent","type":"uint256"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"uint256","name":"podiumDuration","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newEditions","type":"address"}],"name":"setEditions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newLogic","type":"address"}],"name":"setLogic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinFeePercentage","type":"uint256"}],"name":"setMinimumFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newTreasuryConfig","type":"address"}],"name":"setTreasuryConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newTributaryRegistry","type":"address"}],"name":"setTributaryRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nextOwner_","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasuryConfig","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tributaryRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]