diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index b9d6933..60dfc28 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -88,7 +88,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - ghcr.io/settlemint/solidity-empty + ghcr.io/settlemint/solidity-diamond-bond # generate Docker tags based on the following events/attributes tags: | type=schedule diff --git a/.gitignore b/.gitignore index 8dca3a6..2fd807e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,30 @@ docs/ # Subgraphs deployment.txt deployment-anvil.txt +selectors-bond.txt +selectors-erc1155.txt +selectors-loupe.txt +selectors-ownership.txt subgraph/subgraph.config.json subgraph/node_modules subgraph/generated subgraph/build -.pnpm \ No newline at end of file +.pnpm +node_modules +.env + +# Hardhat files +/cache +/artifacts + +# TypeChain files +/typechain +/typechain-types + +# solidity-coverage files +/coverage +/coverage.json + +# Hardhat Ignition default folder for deployments against a local node +ignition/deployments/chain-31337 diff --git a/.gitmodules b/.gitmodules index 888d42d..2d53bc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/prb-math"] + path = lib/prb-math + url = https://github.com/PaulRBerg/prb-math diff --git a/Dockerfile b/Dockerfile index 97e11bb..f480920 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,13 +8,13 @@ WORKDIR / RUN git config --global user.email "hello@settlemint.com" && \ git config --global user.name "SettleMint" && \ - forge init usecase --template settlemint/solidity-empty && \ + forge init usecase --template settlemint/solidity-diamond-bond && \ cd usecase && \ forge build USER root -FROM busybox +FROM cgr.dev/chainguard/busybox:latest COPY --from=build /usecase /usecase COPY --from=build /root/.svm /usecase-svm diff --git a/Makefile b/Makefile index c7894e4..f999550 100644 --- a/Makefile +++ b/Makefile @@ -24,14 +24,14 @@ anvil: @anvil deploy-anvil: - @echo "Deploying with Forge to Anvil..." - @forge create ./src/Counter.sol:Counter --rpc-url anvil --interactive | tee deployment-anvil.txt + @echo "Deploying to Anvil..." + @forge script script/DeployDiamond.s.sol --sender 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url anvil --broadcast --unlocked deploy-btp: @eval $$(curl -H "x-auth-token: $${BTP_SERVICE_TOKEN}" -s $${BTP_CLUSTER_MANAGER_URL}/ide/foundry/$${BTP_SCS_ID}/env | sed 's/^/export /'); \ args=""; \ if [ ! -z "$${BTP_FROM}" ]; then \ - args="--unlocked --from $${BTP_FROM}"; \ + args="--unlocked --sender $${BTP_FROM} --broadcast"; \ else \ echo "\033[1;33mWARNING: No keys are activated on the node, falling back to interactive mode...\033[0m"; \ echo ""; \ @@ -43,27 +43,43 @@ deploy-btp: if [ "$${BTP_EIP_1559_ENABLED}" = "false" ]; then \ args="$$args --legacy"; \ fi; \ - forge create ./src/Counter.sol:Counter $${EXTRA_ARGS} --rpc-url $${BTP_RPC_URL} $$args --constructor-args "GenericToken" "GT" | tee deployment.txt; + forge script script/DeployDiamond.s.sol $${args} --rpc-url $${BTP_RPC_URL} --json -script-anvil: - @if [ ! -f deployment-anvil.txt ]; then \ - echo "\033[1;31mERROR: Contract was not deployed or the deployment-anvil.txt went missing.\033[0m"; \ - exit 1; \ - fi - @DEPLOYED_ADDRESS=$$(grep "Deployed to:" deployment-anvil.txt | awk '{print $$3}') forge script script/Counter.s.sol:CounterScript ${EXTRA_ARGS} --rpc-url anvil -i=1 +subgraph: + @echo "Deploying the subgraph..." + @rm -Rf subgraph/subgraph.config.json + @CHAIN_ID=$$(cast chain-id --rpc-url $$BTP_RPC_URL); \ + output=$$(jq '.transactions[] | \ + select(.transactionType == "CREATE" and \ + (.contractName == "GenericToken" or .contractName == "Diamond")) | \ + if .contractName == "GenericToken" then \ + {contractName: "GenericToken", contractAddress: (.contractAddress // "not available"), transactionHash: (.hash // "not available")} \ + elif .contractName == "Diamond" then \ + {contractName: "Diamond", contractAddress: (.contractAddress // "not available"), transactionHash: (.hash // "not available")} \ + else empty end' broadcast/DeployDiamond.s.sol/$$CHAIN_ID/run-latest.json); \ + export DEPLOYED_ERC20_ADDRESS=$$(echo "$$output" | jq -r 'select(.contractName == "GenericToken") | .contractAddress'); \ + export TRANSACTION_HASH_ERC20=$$(echo "$$output" | jq -r 'select(.contractName == "GenericToken") | .transactionHash'); \ + export DEPLOYED_ADDRESS=$$(echo "$$output" | jq -r 'select(.contractName == "Diamond") | .contractAddress'); \ + export TRANSACTION_HASH=$$(echo "$$output" | jq -r 'select(.contractName == "Diamond") | .transactionHash'); \ + export BLOCK_NUMBER=$$(cast receipt --rpc-url $${BTP_RPC_URL} $${TRANSACTION_HASH} | grep "^blockNumber" | awk '{print $$2}'); \ + export BLOCK_NUMBER_ERC20=$$(cast receipt --rpc-url $${BTP_RPC_URL} $${TRANSACTION_HASH_ERC20} | grep "^blockNumber" | awk '{print $$2}'); \ + yq e -p=json -o=json '.datasources[0].address = strenv(DEPLOYED_ADDRESS) | .datasources[0].startBlock = strenv(BLOCK_NUMBER) | .datasources[1].address = strenv(DEPLOYED_ERC20_ADDRESS) | .datasources[1].startBlock = strenv(BLOCK_NUMBER_ERC20) | .chain = strenv(BTP_NODE_UNIQUE_NAME)' subgraph/subgraph.config.template.json > subgraph/subgraph.config.json; \ -script: - @if [ ! -f deployment.txt ]; then \ - echo "\033[1;31mERROR: Contract was not deployed or the deployment.txt went missing.\033[0m"; \ - exit 1; \ - fi + @cd subgraph && npx graph-compiler --config subgraph.config.json --include node_modules/@openzeppelin/subgraphs/src/datasources ./datasources --export-schema --export-subgraph + @cd subgraph && yq e '.specVersion = "0.0.4"' -i generated/solidity-diamond-bond.subgraph.yaml + @cd subgraph && yq e '.description = "Solidity Token diamond-bond"' -i generated/solidity-diamond-bond.subgraph.yaml + @cd subgraph && yq e '.repository = "https://github.com/settlemint/solidity-diamond-bond"' -i generated/solidity-diamond-bond.subgraph.yaml + @cd subgraph && yq e '.features = ["nonFatalErrors", "fullTextSearch", "ipfsOnEthereumContracts"]' -i generated/solidity-diamond-bond.subgraph.yaml + @cd subgraph && npx graph codegen generated/solidity-diamond-bond.subgraph.yaml + @cd subgraph && npx graph build generated/solidity-diamond-bond.subgraph.yaml @eval $$(curl -H "x-auth-token: $${BTP_SERVICE_TOKEN}" -s $${BTP_CLUSTER_MANAGER_URL}/ide/foundry/$${BTP_SCS_ID}/env | sed 's/^/export /'); \ - if [ -z "${BTP_FROM}" ]; then \ - echo "\033[1;33mWARNING: No keys are activated on the node, falling back to interactive mode...\033[0m"; \ - echo ""; \ - @DEPLOYED_ADDRESS=$$(grep "Deployed to:" deployment.txt | awk '{print $$3}') forge script script/Counter.s.sol:CounterScript ${EXTRA_ARGS} --rpc-url ${BTP_RPC_URL} -i=1; \ + if [ -z "$${BTP_MIDDLEWARE}" ]; then \ + echo "\033[1;31mERROR: You have not launched a graph middleware for this smart contract set, aborting...\033[0m"; \ + exit 1; \ else \ - @DEPLOYED_ADDRESS=$$(grep "Deployed to:" deployment.txt | awk '{print $$3}') forge script script/Counter.s.sol:CounterScript ${EXTRA_ARGS} --rpc-url ${BTP_RPC_URL} --unlocked --froms ${BTP_FROM}; \ + cd subgraph; \ + npx graph create --node $${BTP_MIDDLEWARE} $${BTP_SCS_NAME}; \ + npx graph deploy --version-label v1.0.$$(date +%s) --node $${BTP_MIDDLEWARE} --ipfs $${BTP_IPFS}/api/v0 $${BTP_SCS_NAME} generated/solidity-diamond-bond.subgraph.yaml; \ fi cast: diff --git a/README.md b/README.md index d2a86ea..5ec0dda 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ERC20 +# Diamond Bond -A basic ERC20 token contract. +A Diamond Bond contract. ## Get started @@ -9,7 +9,7 @@ Launch this smart contract set in the SettleMint Blockchain Transformation platf If you want to use it separately, bootstrap a new project using ```shell -forge init my-erc20-token --template settlemint/solidity-token-erc20 +forge init my-diamond-bond --template settlemint/solidity-diamond-bond ``` ## DX: Foundry @@ -25,33 +25,33 @@ Foundry consists of: ## Documentation -- https://console.settlemint.com/documentation/docs/using-platform/integrated-development-environment/ -- https://book.getfoundry.sh/ +- +- ## Usage ### Build ```shell -$ forge build +forge build ``` ### Test ```shell -$ forge test +forge test ``` ### Format ```shell -$ forge fmt +forge fmt ``` ### Gas Snapshots ```shell -$ forge snapshot +forge snapshot ``` ### Anvil @@ -59,7 +59,7 @@ $ forge snapshot Anvil is a local development node, open a terminal in the IDE and launch anvil. You can then deploy to it using `make deploy-anvil` ```shell -$ anvil +anvil ``` ### Deploy @@ -67,7 +67,7 @@ $ anvil Deploy to a local anvil node: ```shell -$ make deploy-anvil +make deploy-anvil ``` When prompted to enter a private key, copy one of the private keys shown in the terminal when you start the anvil node. @@ -75,7 +75,7 @@ When prompted to enter a private key, copy one of the private keys shown in the Deploy to the connected platform node: ```shell -$ make deploy-btp +make deploy-btp ``` If you have a private key activated on the connected node, it will be used automatically. Else, you will be prompted to enter a private key. You can copy-paste a private key from the platform. @@ -83,19 +83,19 @@ If you have a private key activated on the connected node, it will be used autom ### Cast ```shell -$ cast +cast ``` ### Deploy your subgraph ```shell -$ make subgraph +make subgraph ``` ### Help ```shell -$ forge --help -$ anvil --help -$ cast --help +forge --help +anvil --help +cast --help ``` diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/lib/prb-math b/lib/prb-math new file mode 160000 index 0000000..9dc0651 --- /dev/null +++ b/lib/prb-math @@ -0,0 +1 @@ +Subproject commit 9dc06519f3b9f1659fec7d396da634fe690f660c diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..ceeb85d --- /dev/null +++ b/remappings.txt @@ -0,0 +1,7 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@prb/test/=lib/prb-math/node_modules/@prb/test/ +ds-test/=lib/forge-std/lib/ds-test/src/ +erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +openzeppelin-contracts/=lib/openzeppelin-contracts/ +prb-math/=lib/prb-math/src/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 0c646b9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {Script, console} from "forge-std/Script.sol"; -import "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; // Instance of the Counter contract - - function setUp() public { - string memory key = "DEPLOYED_ADDRESS"; - counter = Counter(vm.envAddress(key)); - } - - function run() public { - vm.startBroadcast(); - - counter.setNumber(5); // Set the initial number - - uint256 initialCount = counter.number(); // Read the current value of the counter - console.log("Current Counter Value:", initialCount); // Log the current counter value - - counter.increment(); // Call the increment function of the Counter contract - - uint256 currentCount = counter.number(); // Read the current value of the counter - console.log("Current Counter Value:", currentCount); // Log the current counter value - } -} \ No newline at end of file diff --git a/script/DeployDiamond.s.sol b/script/DeployDiamond.s.sol new file mode 100644 index 0000000..fdddbfa --- /dev/null +++ b/script/DeployDiamond.s.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Script.sol"; +import "../src/facets/BondFacet.sol"; +import "../src/facets/ERC1155Facet.sol"; +import "../src/facets/DiamondCutFacet.sol"; +import "../src/facets/DiamondLoupeFacet.sol"; +import "../src/Diamond.sol"; +import "../src/upgradeInitializers/DiamondInit.sol"; +import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; +import {BondInitParams} from "../src/libraries/StructBondInit.sol"; +import "../src/interfaces/IDiamond.sol"; +import "../src/GenericToken.sol"; + +contract DeployDiamondScript is Script { + function run() public { + vm.startBroadcast(); + address owner = msg.sender; + console.log(owner); + + DiamondCutFacet diamondCut = new DiamondCutFacet(); + address diamondCutAddress = address(diamondCut); + + DiamondInit diamondInit = new DiamondInit(); + address diamondInitAddress = address(diamondInit); + + ERC1155Facet erc1155Facet = new ERC1155Facet(); + address erc1155FacetAddress = address(erc1155Facet); + + DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet(); + address diamondLoupeFacetAddress = address(diamondLoupeFacet); + + BondFacet bondFacet = new BondFacet(); + address bondFacetAddress = address(bondFacet); + + IDiamond.FacetCut[] memory cuts = new IDiamond.FacetCut[](3); + cuts[0] = IDiamond.FacetCut({ + facetAddress: erc1155FacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: erc1155Facet.getSelectors() + }); + + cuts[1] = IDiamond.FacetCut({ + facetAddress: diamondLoupeFacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: diamondLoupeFacet.getSelectors() + }); + + cuts[2] = IDiamond.FacetCut({ + facetAddress: bondFacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: bondFacet.getSelectors() + }); + + DiamondArgs memory da = DiamondArgs({ + owner: owner, + init: diamondInitAddress, + initCalldata: abi.encodeWithSelector(bytes4(keccak256("init()"))) + }); + + Diamond diamond = new Diamond(cuts, da); + address diamondAddress = address(diamond); + + new GenericToken("GenericToken", "GT"); + + vm.stopBroadcast(); + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index fbd0176..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/Diamond.sol b/src/Diamond.sol new file mode 100644 index 0000000..f1cd75d --- /dev/null +++ b/src/Diamond.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//******************************************************************************\ +//* Author: Nick Mudge (https://twitter.com/mudgen) +//* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +//* +//* Implementation of a diamond. +//******************************************************************************/ + +import {LibDiamond} from "./libraries/LibDiamond.sol"; +import {IDiamondCut} from "./interfaces/IDiamondCut.sol"; +import {IDiamondLoupe} from "./interfaces/IDiamondLoupe.sol"; +import {IERC173} from "./interfaces/IERC173.sol"; + +// When no function exists for function called +error FunctionNotFound(bytes4 _functionSelector); + +// This is used in diamond constructor +// more arguments are added to this struct +// this avoids stack too deep errors +struct DiamondArgs { + address owner; + address init; + bytes initCalldata; +} + +contract Diamond { + constructor( + IDiamondCut.FacetCut[] memory _diamondCut, + DiamondArgs memory _args + ) payable { + LibDiamond.setContractOwner(_args.owner); + LibDiamond.diamondCut(_diamondCut, _args.init, _args.initCalldata); + + // Code can be added here to perform actions and set state variables. + } + + // Find facet for function that is called and execute the + // function if a facet is found and return any value. + fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; + // get diamond storage + assembly { + ds.slot := position + } + // get facet from function selector + address facet = ds + .facetAddressAndSelectorPosition[msg.sig] + .facetAddress; + if (facet == address(0)) { + revert FunctionNotFound(msg.sig); + } + // Execute external function from facet using delegatecall and return any value. + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + receive() external payable {} +} diff --git a/src/GenericToken.sol b/src/GenericToken.sol new file mode 100644 index 0000000..2ef7e84 --- /dev/null +++ b/src/GenericToken.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// SettleMint.com + +pragma solidity ^0.8.9; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; +import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +/** + * @title GenericToken + * @notice This contract is a generic token adhering to the ERC20 standard, + * using the OpenZeppelin template libary for battletested functionality. + * + * It incorporates the standard ERC20 functions, enhanced with Minting + * and Burning, Pausable in case of emergencies and AccessControl for locking + * down the administrative functions. + * + * For demonstrative purposes, 1 million GT tokens are pre-mined to the address + * deploying this contract. + */ +contract GenericToken is ERC20, ERC20Burnable { + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { + _mint(msg.sender, 1_000_000 * 10 ** decimals()); + } + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the total supply. + * + * Emits a Transfer event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + * + * @param amount The amount of tokens to burn from the sender of the transaction, denominated by the + * decimals() function + */ + function burn(uint256 amount) public virtual override { + _burn(_msgSender(), amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of `from`'s tokens will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of `from`'s tokens will be burned. + * - `from` and `to` are never both zero. + */ + function _update(address from, address to, uint256 amount) internal override { + super._update(from, to, amount); + } +} diff --git a/src/facets/BondFacet.sol b/src/facets/BondFacet.sol new file mode 100644 index 0000000..7edb770 --- /dev/null +++ b/src/facets/BondFacet.sol @@ -0,0 +1,1184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {ERC1155Facet} from "./ERC1155Facet.sol"; +import "prb-math/UD60x18.sol"; +import {BokkyPooBahsDateTimeLibrary} from "../libraries/BokkyPooBahsDateTimeLibrary.sol"; +import {BondInitParams} from "../libraries/StructBondInit.sol"; +import {BondStorage} from "./BondStorage.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract BondFacet is BondStorage { + ERC1155Facet private __bond; + address private __currencyAddress; + + // Events + event BondInitialized( + uint256 bondId, + uint256 coupure, + uint256 interestNum, + uint256 interestDen, + uint256 withholdingTaxNum, + uint256 withholdingTaxDen, + uint256 periodicInterestRate, + uint256 netReturn, + uint256 periodicity, + uint256 duration, + uint256 methodOfRepayment, + uint256 maxSupply, + uint256 formOfFinancing + ); + + event MinAndMaxAmountSet( + uint256 bondId, + uint256 minAmount, + uint256 maxAmount, + uint256 maxAmountPerInvestor + ); + event BondParametersEdited( + uint256 bondId, + uint256 coupure, + uint256 interestNum, + uint256 interestDen, + uint256 withholdingTaxNum, + uint256 withholdingTaxDen, + uint256 periodicInterestRate, + uint256 netReturn, + uint256 periodicity, + uint256 duration, + uint256 methodOfRepayment, + uint256 maxSupply, + uint256 formOfFinancing + ); + + event CampaignStartAndEndDateSet( + uint256 bondId, + uint256 startDate, + uint256 endDate + ); + event IssueDateSet(uint256 bondId, uint256 issueDate); + + event CouponsComputed( + uint256 bondId, + uint256[] couponDates, + uint256[] remainingCapital, + uint256[] capitalRepayments, + uint256[] grossCouponRates, + uint256[] netCouponRates + ); + + event BondIssued(uint256 bondId, uint256 timestamp, uint256 issuedAmount); + event BondsWithdrawn( + string bondPurchaseId, + uint256 bondId, + address holder, + uint256 amount + ); + event BalloonRateSet( + uint256 bondId, + uint256 balloonRateNum, + uint256 balloonRateDen + ); + event GracePeriodSet(uint256 bondId, uint256 gracePeriodDuration); + event CapitalAmortizationFreePeriodSet( + uint256 bondId, + uint256 capitalAmortizationFreePeriodDuration + ); + event InvestorsCountChanged(uint256 bondId, uint256 investorsCount); + event RevocationsCountChanged(uint256 bondId, uint256 revocationsCount); + + event CampaignPaused(uint256 bondId); + event CampaignUnpaused(uint256 bondId); + + event BondTerminated(uint256 bondId); + event PeriodicInterestRateSet(uint256 bondId, uint256 periodicInterest); + + event BondTransferred( + string bondTransferId, + uint256 bondId, + address oldAccount, + address newAccount, + uint256 amount + ); + event ReservedAmountChanged(uint256 bondId, uint256 reservedAmount); + + event CapitalClaimAmountSet( + uint256 bondId, + string capitalClaimId, + uint256 capitalAmount + ); + + event CouponStatusChanged(uint256 bondId, uint256 lineNumber); + + // Errors + error CampaignIsPaused(); + error CampaignNotPaused(); + error CampaignAlreadyPaused(); + error CampaignIsClosed(); + + error BondAlreadyInitialized(); + error BondAlreadyIssued(); + error BondHasNotBeenIssued(); + error NoMoreBondsToBuy(); + + error DurationIsNotAMultpleOfTwelve(); + error DurationIsNotAMultpleOfThree(); + + error GracePeriodDurationIsNotAMultpleOfTwelve(); + error GracePeriodDurationIsNotAMultpleOfThree(); + + error CapitalAmortizationFreePeriodDurationIsNotAMultpleOfTwelve(); + error CapitalAmortizationFreePeriodDurationIsNotAMultpleOfThree(); + + error OldAccountDoesNotHaveEnoughBonds(); + + error CannotReserveBeforeSignupDate(); + error ExceedingMaxAmountPerInvestor(); + error NotAllClaimsReceivedForNextPayment(); + error DivideByZero(); + + modifier campaignNotPaused(uint256 _bondId) { + BondParams storage _bondDetails = bondStorage(_bondId); + if (_bondDetails.__paused) { + revert CampaignIsPaused(); + } + _; + } + + function setCurrencyAddress(address _currencyAddress) external { + __currencyAddress = _currencyAddress; + } + + function setCouponDatesFromIssueDate( + uint256 _bondId, + uint256 _issueTimeStamp + ) internal { + BondParams storage _bondDetails = bondStorage(_bondId); + if (_bondDetails.__issued == true) { + revert BondAlreadyIssued(); + } + uint256 year; + uint256 month; + uint256 day; + (year, month, day) = BokkyPooBahsDateTimeLibrary.timestampToDate( + _issueTimeStamp + ); + + uint256 nbrOfPayments; + if (_bondDetails.__periodicity == Periodicity.Annual) { + nbrOfPayments = _bondDetails.__duration / 12; + } else if (_bondDetails.__periodicity == Periodicity.Quarterly) { + nbrOfPayments = _bondDetails.__duration / 3; + } else { + nbrOfPayments = _bondDetails.__duration; + } + + uint256 couponMonth; + uint256 couponYear = year; + uint256 couponDay = day; + + delete _bondDetails.__couponDates; + delete _bondDetails.__couponStatus; + + for (uint256 i = 0; i < nbrOfPayments; ++i) { + if (_bondDetails.__periodicity == Periodicity.Monthly) { + if (i == 0) { + if (month % 12 == 0) { + couponYear = year + 1; + } else { + couponYear = year; + } + couponMonth = (month + 1) % 12; + if (couponMonth == 0) { + couponMonth = 12; + } + } else { + if (couponMonth % 12 == 0) { + couponYear = couponYear + 1; + } else { + couponYear = couponYear; + } + couponMonth = (couponMonth + 1) % 12; + if (couponMonth == 0) { + couponMonth = 12; + } + } + } else if (_bondDetails.__periodicity == Periodicity.Quarterly) { + if (i == 0) { + if (month >= 11) { + couponYear = year + 1; + } else { + couponYear = year; + } + couponMonth = (month + 3) % 12; + if (couponMonth == 0) { + couponMonth = 12; + } + } else { + if (couponMonth >= 10) { + couponYear = couponYear + 1; + } + couponMonth = (couponMonth + 3) % 12; + if (couponMonth == 0) { + couponMonth = 12; + } + } + } else if (_bondDetails.__periodicity == Periodicity.Annual) { + if (i == 0) { + if (month == 12) { + couponYear = year + 1; + } + couponMonth = (month) % 12; + if (couponMonth == 0) { + couponMonth = 12; + } + } else { + couponYear = couponYear + 1; + } + } + _bondDetails.__couponDates.push( + BokkyPooBahsDateTimeLibrary.timestampFromDate( + couponYear, + couponMonth, + couponDay + ) + ); + + if (i == nbrOfPayments - 1) { + _bondDetails.__maturityDate = BokkyPooBahsDateTimeLibrary + .timestampFromDate(couponYear, couponMonth, couponDay); + } + _bondDetails.__couponStatus.push(CouponStatus.Todo); + } + + _bondDetails.__issueDate = _issueTimeStamp; + emit IssueDateSet(_bondId, _issueTimeStamp); + } + + function setCouponRates( + uint256 _bondId + ) internal returns (uint256[] memory, uint256[] memory, uint256[] memory) { + BondParams storage _bondDetails = bondStorage(_bondId); + if (_bondDetails.__issued == true) { + revert BondAlreadyIssued(); + } + + uint256 nbrOfPayments; + uint256 capitalRepayment; + uint256 remainingCapital = _bondDetails.__coupure; + if (_bondDetails.__periodicity == Periodicity.Annual) { + nbrOfPayments = _bondDetails.__duration / 12; + } else if (_bondDetails.__periodicity == Periodicity.Quarterly) { + nbrOfPayments = _bondDetails.__duration / 3; + } else { + nbrOfPayments = _bondDetails.__duration; + } + + delete _bondDetails.__grossCouponRates; + delete _bondDetails.__netCouponRates; + delete _bondDetails.__capitalRepayment; + delete _bondDetails.__remainingCapital; + + _bondDetails.__capitalRepayment.push(0); + _bondDetails.__remainingCapital.push(_bondDetails.__coupure); + _bondDetails.__grossCouponRates.push(0); + _bondDetails.__netCouponRates.push(0); + _bondDetails.__totalToBeRepaid = _bondDetails.__coupure; + + for (uint256 i = 0; i < nbrOfPayments; ++i) { + if (_bondDetails.__methodOfRepayment == MethodOfRepayment.Bullet) { + capitalRepayment = 0; + } else if ( + _bondDetails.__methodOfRepayment == MethodOfRepayment.Degressive + ) { + capitalRepayment = convert( + div(ud60x18(_bondDetails.__coupure), ud60x18(nbrOfPayments)) + ); + } else if ( + _bondDetails.__methodOfRepayment == + MethodOfRepayment.WithCapitalAmortizationFreePeriod || + _bondDetails.__methodOfRepayment == + MethodOfRepayment.CapitalAmortizationAndGracePeriod + ) { + if (_bondDetails.__periodicity == Periodicity.Annual) { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__capitalAmortizationDuration / + 12 + ) + ) + ); + } else if ( + _bondDetails.__periodicity == Periodicity.Quarterly + ) { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__capitalAmortizationDuration / + 3 + ) + ) + ); + } else { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__capitalAmortizationDuration + ) + ) + ); + } + } else if ( + _bondDetails.__methodOfRepayment == + MethodOfRepayment.GracePeriod + ) { + if (_bondDetails.__periodicity == Periodicity.Annual) { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__gracePeriodDuration / + 12 + ) + ) + ); + } else if ( + _bondDetails.__periodicity == Periodicity.Quarterly + ) { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__gracePeriodDuration / + 3 + ) + ) + ); + } else { + capitalRepayment = convert( + div( + ud60x18(_bondDetails.__coupure), + ud60x18( + nbrOfPayments - + _bondDetails.__gracePeriodDuration + ) + ) + ); + } + } + + uint256 grossInterest = convert( + mul( + ud60x18(_bondDetails.__periodicInterestRate), + ud60x18(remainingCapital) + ) + ); + UD60x18 taxMultiplier = ud60x18(1) - + ud60x18(_bondDetails.__withholdingTax); + UD60x18 taxableInterest = mul( + ud60x18(grossInterest), + taxMultiplier + ); + uint256 netInterest = convert(taxableInterest); + + if (_bondDetails.__methodOfRepayment == MethodOfRepayment.Bullet) { + if (i < nbrOfPayments - 1) { + _bondDetails.__capitalRepayment.push(0); + _bondDetails.__remainingCapital.push(remainingCapital); + } else { + _bondDetails.__remainingCapital.push(0); + _bondDetails.__capitalRepayment.push(remainingCapital); + } + } else if ( + _bondDetails.__methodOfRepayment == MethodOfRepayment.Balloon + ) { + if (i == 0) { + uint256 balloonRate = _bondDetails.__balloonRate; + UD60x18 temp = mul( + ud60x18(balloonRate), + ud60x18(remainingCapital) + ); + remainingCapital = remainingCapital - convert(temp); + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push(convert(temp)); + } else if (i == nbrOfPayments - 1) { + _bondDetails.__remainingCapital.push(0); + _bondDetails.__capitalRepayment.push(remainingCapital); + } else { + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push(0); + } + } else if ( + _bondDetails.__methodOfRepayment == MethodOfRepayment.Degressive + ) { + if (i < nbrOfPayments - 1) { + remainingCapital = remainingCapital - capitalRepayment; + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push(capitalRepayment); + } else { + _bondDetails.__remainingCapital.push(0); + _bondDetails.__capitalRepayment.push( + _bondDetails.__remainingCapital[i] + ); + } + } else if ( + _bondDetails.__methodOfRepayment == + MethodOfRepayment.WithCapitalAmortizationFreePeriod + ) { + if ( + (_bondDetails.__periodicity == Periodicity.Annual && + i >= _bondDetails.__capitalAmortizationDuration / 12) || + (_bondDetails.__periodicity == Periodicity.Quarterly && + i >= _bondDetails.__capitalAmortizationDuration / 3) || + (_bondDetails.__periodicity == Periodicity.Monthly && + i >= _bondDetails.__capitalAmortizationDuration) + ) { + if (i < nbrOfPayments - 1) { + remainingCapital = remainingCapital - capitalRepayment; + _bondDetails.__capitalRepayment.push(capitalRepayment); + _bondDetails.__remainingCapital.push(remainingCapital); + } else { + remainingCapital = 0; + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push( + _bondDetails.__remainingCapital[i] + ); + } + } else { + _bondDetails.__capitalRepayment.push(0); + _bondDetails.__remainingCapital.push(remainingCapital); + } + } else if ( + _bondDetails.__methodOfRepayment == + MethodOfRepayment.GracePeriod + ) { + if ( + (_bondDetails.__periodicity == Periodicity.Annual && + i >= _bondDetails.__gracePeriodDuration / 12) || + (_bondDetails.__periodicity == Periodicity.Quarterly && + i >= _bondDetails.__gracePeriodDuration / 3) || + (_bondDetails.__periodicity == Periodicity.Monthly && + i >= _bondDetails.__gracePeriodDuration) + ) { + if (i < nbrOfPayments - 1) { + remainingCapital = remainingCapital - capitalRepayment; + _bondDetails.__capitalRepayment.push(capitalRepayment); + _bondDetails.__remainingCapital.push(remainingCapital); + } else { + remainingCapital = 0; + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push( + _bondDetails.__remainingCapital[i] + ); + } + } else { + grossInterest = 0; + netInterest = 0; + _bondDetails.__capitalRepayment.push(0); + _bondDetails.__remainingCapital.push(remainingCapital); + } + } else if ( + _bondDetails.__methodOfRepayment == + MethodOfRepayment.CapitalAmortizationAndGracePeriod + ) { + if ( + (_bondDetails.__periodicity == Periodicity.Annual && + i >= _bondDetails.__capitalAmortizationDuration / 12) || + (_bondDetails.__periodicity == Periodicity.Quarterly && + i >= _bondDetails.__capitalAmortizationDuration / 3) || + (_bondDetails.__periodicity == Periodicity.Monthly && + i >= _bondDetails.__capitalAmortizationDuration) + ) { + if (i < nbrOfPayments - 1) { + remainingCapital = remainingCapital - capitalRepayment; + _bondDetails.__capitalRepayment.push(capitalRepayment); + _bondDetails.__remainingCapital.push(remainingCapital); + } else { + remainingCapital = 0; + _bondDetails.__remainingCapital.push(remainingCapital); + _bondDetails.__capitalRepayment.push( + _bondDetails.__remainingCapital[i] + ); + } + } else { + _bondDetails.__capitalRepayment.push(0); + _bondDetails.__remainingCapital.push(remainingCapital); + } + if ( + (_bondDetails.__periodicity == Periodicity.Annual && + i < _bondDetails.__gracePeriodDuration / 12) || + (_bondDetails.__periodicity == Periodicity.Quarterly && + i < _bondDetails.__gracePeriodDuration / 3) || + (_bondDetails.__periodicity == Periodicity.Monthly && + i < _bondDetails.__gracePeriodDuration) + ) { + grossInterest = 0; + netInterest = 0; + } + } + _bondDetails.__grossCouponRates.push(grossInterest); + + _bondDetails.__netCouponRates.push(netInterest); + _bondDetails.__totalToBeRepaid += netInterest; + } + + if (_bondDetails.__periodicInterestRate < 1) { + emit PeriodicInterestRateSet( + _bondId, + _bondDetails.__periodicInterestRate + ); + } else { + emit PeriodicInterestRateSet( + _bondId, + _bondDetails.__periodicInterestRate + ); + } + emit CouponsComputed( + _bondId, + _bondDetails.__couponDates, + _bondDetails.__remainingCapital, + _bondDetails.__capitalRepayment, + _bondDetails.__grossCouponRates, + _bondDetails.__netCouponRates + ); + return ( + _bondDetails.__couponDates, + _bondDetails.__grossCouponRates, + _bondDetails.__netCouponRates + ); + } + + function setParameters( + BondInitParams.BondInit memory bi, + bool replacementBond + ) internal { + //BondDetails storage _bondDetails = __bondDetails[bi.__bondId]; + BondParams storage _bondDetails = bondStorage(bi.__bondId); + _bondDetails.__coupure = bi.__coupure; + if (bi.__interestDen == 0) { + revert DivideByZero(); + } + if (bi.__periodicity == uint256(Periodicity.Annual)) { + if (bi.__duration % 12 != 0) { + revert DurationIsNotAMultpleOfTwelve(); + } + } else if (bi.__periodicity == uint256(Periodicity.Quarterly)) { + if (bi.__duration % 3 != 0) { + revert DurationIsNotAMultpleOfThree(); + } + } + if (bi.__gracePeriodDuration != 0) { + if (bi.__periodicity == uint256(Periodicity.Annual)) { + if (bi.__gracePeriodDuration % 12 != 0) { + revert GracePeriodDurationIsNotAMultpleOfTwelve(); + } + } else if (bi.__periodicity == uint256(Periodicity.Quarterly)) { + if (bi.__gracePeriodDuration % 3 != 0) { + revert GracePeriodDurationIsNotAMultpleOfThree(); + } + } + } + if (bi.__capitalAmortizationDuration != 0) { + if (bi.__periodicity == uint256(Periodicity.Annual)) { + if (bi.__capitalAmortizationDuration % 12 != 0) { + revert CapitalAmortizationFreePeriodDurationIsNotAMultpleOfTwelve(); + } + } else if (bi.__periodicity == uint256(Periodicity.Quarterly)) { + if (bi.__capitalAmortizationDuration % 3 != 0) { + revert CapitalAmortizationFreePeriodDurationIsNotAMultpleOfThree(); + } + } + } + _bondDetails.__interestRate = convert( + div(ud60x18(bi.__interestNum), ud60x18(bi.__interestDen)) + ); + _bondDetails.__withholdingTax = convert( + div( + ud60x18(bi.__withholdingTaxNum), + ud60x18(bi.__withholdingTaxDen) + ) + ); + _bondDetails.__campaignMaxAmount = bi.__campaignMaxAmount; + _bondDetails.__campaignMinAmount = bi.__campaignMinAmount; + _bondDetails.__maxSupply = convert( + div(ud60x18(bi.__campaignMaxAmount), ud60x18(bi.__coupure)) + ); + //_bondDetails.__maxAmountPerInvestor = ud60x18(bi.__maxAmountPerInvestor); + _bondDetails.__maxAmountPerInvestor = bi.__maxAmountPerInvestor; + _bondDetails.__duration = bi.__duration; + + _bondDetails.__campaignStartDate = bi.__campaignStartDate; + _bondDetails.__campaignEndDate = BokkyPooBahsDateTimeLibrary.addDays( + bi.__campaignStartDate, + 60 + ); + + if (bi.__periodicity == uint256(Periodicity.Annual)) { + _bondDetails.__periodicity = Periodicity.Annual; + _bondDetails.__periodicInterestRate = _bondDetails.__interestRate; + } else if (bi.__periodicity == uint256(Periodicity.Quarterly)) { + _bondDetails.__periodicity = Periodicity.Quarterly; + UD60x18 a = ud60x18(bi.__interestDen + bi.__interestNum); + UD60x18 b = ud60x18(bi.__interestDen); + UD60x18 c = ud60x18(1); + UD60x18 d = ud60x18(4); + _bondDetails.__periodicInterestRate = convert( + pow(div(a, b), div(c, d)) - ud60x18(1) + ); + } else if (bi.__periodicity == uint256(Periodicity.Monthly)) { + _bondDetails.__periodicity = Periodicity.Monthly; + UD60x18 a = ud60x18(bi.__interestDen + bi.__interestNum); + UD60x18 b = ud60x18(bi.__interestDen); + UD60x18 c = ud60x18(1); + UD60x18 d = ud60x18(12); + _bondDetails.__periodicInterestRate = convert( + pow(div(a, b), div(c, d)) - ud60x18(1) + ); + } + + if (bi.__methodOfRepayment == uint256(MethodOfRepayment.Degressive)) { + if (bi.__capitalAmortizationDuration != 0) { + _bondDetails.__methodOfRepayment = MethodOfRepayment + .WithCapitalAmortizationFreePeriod; + } else if (bi.__gracePeriodDuration != 0) { + _bondDetails.__methodOfRepayment = MethodOfRepayment + .GracePeriod; + } else { + _bondDetails.__methodOfRepayment = MethodOfRepayment.Degressive; + } + } else if ( + bi.__methodOfRepayment == uint256(MethodOfRepayment.Bullet) + ) { + _bondDetails.__methodOfRepayment = MethodOfRepayment.Bullet; + } else if ( + bi.__methodOfRepayment == uint256(MethodOfRepayment.Balloon) + ) { + _bondDetails.__methodOfRepayment = MethodOfRepayment.Balloon; + } + _bondDetails.__capitalAmortizationDuration = bi + .__capitalAmortizationDuration; + _bondDetails.__gracePeriodDuration = bi.__gracePeriodDuration; + if (bi.__balloonRateNum != 0 && bi.__balloonRateDen != 0) { + _bondDetails.__balloonRate = convert( + div(ud60x18(bi.__balloonRateNum), ud60x18(bi.__balloonRateDen)) + ); + } + + _bondDetails.__isSub = replacementBond; + + _bondDetails.__netReturn = + _bondDetails.__interestRate - + convert( + mul( + ud60x18(_bondDetails.__interestRate), + ud60x18(_bondDetails.__withholdingTax) + ) + ); + + emit GracePeriodSet(bi.__bondId, bi.__gracePeriodDuration); + emit BalloonRateSet( + bi.__bondId, + bi.__balloonRateNum, + bi.__balloonRateDen + ); + emit CapitalAmortizationFreePeriodSet( + bi.__bondId, + bi.__capitalAmortizationDuration + ); + emit MinAndMaxAmountSet( + bi.__bondId, + _bondDetails.__campaignMinAmount, + _bondDetails.__campaignMaxAmount, + _bondDetails.__maxAmountPerInvestor + ); + emit CampaignStartAndEndDateSet( + bi.__bondId, + _bondDetails.__campaignStartDate, + _bondDetails.__campaignEndDate + ); + } + + function initializeBond(BondInitParams.BondInit memory bi) external { + //BondDetails storage _bondDetails = __bondDetails[bi.__bondId]; + __bond = ERC1155Facet(address(this)); + BondParams storage _bondDetails = bondStorage(bi.__bondId); + if (_bondDetails.__initDone) { + revert BondAlreadyInitialized(); + } + setParameters(bi, false); + _bondDetails.__initDone = true; + + if (ud60x18(_bondDetails.__periodicInterestRate) < ud60x18(1)) { + emit BondInitialized( + bi.__bondId, + bi.__coupure, + bi.__interestNum, + bi.__interestDen, + bi.__withholdingTaxNum, + bi.__withholdingTaxDen, + _bondDetails.__periodicInterestRate, + _bondDetails.__netReturn, + bi.__periodicity, + bi.__duration, + bi.__methodOfRepayment, + _bondDetails.__maxSupply, + uint256(_bondDetails.__formOfFinancing) + ); + } else { + emit BondInitialized( + bi.__bondId, + bi.__coupure, + bi.__interestNum, + bi.__interestDen, + bi.__withholdingTaxNum, + bi.__withholdingTaxDen, + _bondDetails.__periodicInterestRate, + _bondDetails.__netReturn, + bi.__periodicity, + bi.__duration, + bi.__methodOfRepayment, + _bondDetails.__maxSupply, + uint256(_bondDetails.__formOfFinancing) + ); + } + setCouponDatesFromIssueDate(bi.__bondId, bi.__expectedIssueDate); + setCouponRates(bi.__bondId); + } + + function editBondParameters(BondInitParams.BondInit memory bi) external { + //BondDetails storage _bondDetails = __bondDetails[bi.__bondId]; + BondParams storage _bondDetails = bondStorage(bi.__bondId); + if (_bondDetails.__issued) { + revert BondAlreadyIssued(); + } + setParameters(bi, false); + emit CampaignStartAndEndDateSet( + bi.__bondId, + _bondDetails.__campaignStartDate, + _bondDetails.__campaignEndDate + ); + emit MinAndMaxAmountSet( + bi.__bondId, + _bondDetails.__campaignMinAmount, + _bondDetails.__campaignMaxAmount, + _bondDetails.__maxAmountPerInvestor + ); + if (ud60x18(_bondDetails.__periodicInterestRate) < ud60x18(1)) { + emit BondParametersEdited( + bi.__bondId, + bi.__coupure, + bi.__interestNum, + bi.__interestDen, + bi.__withholdingTaxNum, + bi.__withholdingTaxDen, + _bondDetails.__periodicInterestRate, + _bondDetails.__netReturn, + bi.__periodicity, + bi.__duration, + bi.__methodOfRepayment, + _bondDetails.__maxSupply, + uint256(_bondDetails.__formOfFinancing) + ); + } else { + emit BondParametersEdited( + bi.__bondId, + bi.__coupure, + bi.__interestNum, + bi.__interestDen, + bi.__withholdingTaxNum, + bi.__withholdingTaxDen, + _bondDetails.__periodicInterestRate, + _bondDetails.__netReturn, + bi.__periodicity, + bi.__duration, + bi.__methodOfRepayment, + _bondDetails.__maxSupply, + uint256(_bondDetails.__formOfFinancing) + ); + } + setCouponDatesFromIssueDate(bi.__bondId, bi.__expectedIssueDate); + setCouponRates(bi.__bondId); + } + + function cancel(uint256 _bondId) external { + BondParams storage _bondDetails = bondStorage(_bondId); + + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + _bondDetails.__cancelled = true; + //_bondDetails2.__cancelled = true; + } + + function setBalloonRate( + uint256 _bondId, + uint256 _balloonRateNum, + uint256 _balloonRateDen + ) external { + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__balloonRate = convert( + div(ud60x18(_balloonRateNum), ud60x18(_balloonRateDen)) + ); + emit BalloonRateSet(_bondId, _balloonRateNum, _balloonRateDen); + } + + function setCapitalAmortizationFreeDuration( + uint256 _bondId, + uint256 _duration + ) external { + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__capitalAmortizationDuration = _duration; + emit CapitalAmortizationFreePeriodSet(_bondId, _duration); + } + + function setGracePeriodDuration( + uint256 _bondId, + uint256 _duration + ) external { + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__gracePeriodDuration = _duration; + emit GracePeriodSet(_bondId, _duration); + } + + function setInterestRate( + uint256 _bondId, + uint256 _interestNum, + uint256 _interestDen + ) external { + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__interestRate = convert( + div(ud60x18(_interestNum), ud60x18(_interestDen)) + ); + } + + function getCouponsDates( + uint256 _bondId + ) + external + view + returns (uint256[] memory, uint256[] memory, uint256[] memory) + { + //BondDetails storage _bondDetails = __bondDetails[_bondId]; + BondParams storage _bondDetails = bondStorage(_bondId); + uint256 dateLength = _bondDetails.__couponDates.length; + uint256[] memory day = new uint256[](dateLength); + uint256[] memory month = new uint256[](dateLength); + uint256[] memory year = new uint256[](dateLength); + for (uint256 i = 0; i < dateLength; i++) { + (uint256 y, uint256 m, uint256 d) = BokkyPooBahsDateTimeLibrary + .timestampToDate(_bondDetails.__couponDates[i]); + day[i] = d; + month[i] = m; + year[i] = y; + } + return (day, month, year); + } + + function getCouponsRates( + uint256 _bondId + ) + external + view + returns ( + uint256[] memory, + uint256[] memory, + uint256[] memory, + uint256[] memory + ) + { + BondParams storage _bondDetails = bondStorage(_bondId); + + uint256[] memory gross = new uint256[]( + _bondDetails.__grossCouponRates.length + ); + uint256[] memory net = new uint256[]( + _bondDetails.__grossCouponRates.length + ); + uint256[] memory capital = new uint256[]( + _bondDetails.__capitalRepayment.length + ); + uint256[] memory remainingCapital = new uint256[]( + _bondDetails.__remainingCapital.length + ); + for (uint256 i = 0; i < _bondDetails.__grossCouponRates.length; i++) { + gross[i] = _bondDetails.__grossCouponRates[i]; + net[i] = _bondDetails.__netCouponRates[i]; + capital[i] = _bondDetails.__capitalRepayment[i]; + remainingCapital[i] = _bondDetails.__remainingCapital[i]; + } + return (gross, net, capital, remainingCapital); + } + + function reserve( + string memory _bondPurchaseId, + uint256 _bondId, + uint256 _bondAmount, + address _buyer + ) external campaignNotPaused(_bondId) returns (uint256) { + BondParams storage _bondDetails = bondStorage(_bondId); + uint256 availableAmountOfBonds = _bondDetails.__maxSupply - + _bondDetails.__reservedAmount; + uint256 actualAmountOfBonds; + if (_bondAmount > availableAmountOfBonds) { + actualAmountOfBonds = availableAmountOfBonds; + } else { + actualAmountOfBonds = _bondAmount; + } + + if (block.timestamp < _bondDetails.__campaignStartDate) { + revert CannotReserveBeforeSignupDate(); + } + if (actualAmountOfBonds == 0) { + revert NoMoreBondsToBuy(); + } + if ( + _bondDetails.__reservedAmountByAddress[_buyer] + + actualAmountOfBonds > + _bondDetails.__maxAmountPerInvestor + ) { + revert ExceedingMaxAmountPerInvestor(); + } + if (_bondDetails.__reservedAmountByAddress[_buyer] == 0) { + _bondDetails.__investorsCount += 1; + emit InvestorsCountChanged(_bondId, _bondDetails.__investorsCount); + } + _bondDetails.__reservedAmount += actualAmountOfBonds; + _bondDetails.__reservedAmountByAddress[_buyer] += actualAmountOfBonds; + _bondDetails.__reservedAmountByPurchaseId[ + _bondPurchaseId + ] = actualAmountOfBonds; + emit ReservedAmountChanged(_bondId, _bondDetails.__reservedAmount); + return actualAmountOfBonds; + } + + function pauseCampaign(uint256 _bondId) external { + BondParams storage _bondDetails = bondStorage(_bondId); + if (_bondDetails.__paused) { + revert CampaignAlreadyPaused(); + } + + if ( + _bondDetails.__campaignStartDate >= block.timestamp || + _bondDetails.__campaignEndDate <= block.timestamp + ) { + revert CampaignIsClosed(); + } + _bondDetails.__paused = true; + emit CampaignPaused(_bondId); + } + + function unpauseCampaign(uint256 _bondId) external { + BondParams storage _bondDetails = bondStorage(_bondId); + if (!_bondDetails.__paused) { + revert CampaignNotPaused(); + } + if ( + _bondDetails.__campaignStartDate >= block.timestamp || + _bondDetails.__campaignEndDate <= block.timestamp + ) { + revert CampaignIsClosed(); + } + _bondDetails.__paused = false; + emit CampaignUnpaused(_bondId); + } + + function rescindReservation( + string memory _bondPurchaseId, + uint256 _bondId, + address _buyer + ) external { + BondParams storage _bondDetails = bondStorage(_bondId); + + /*require( + __bondDetails[_bondId].__reservedAmountByPurchaseId[_bondPurchaseId] != 0, + "BondFacet: Reservation does not exist" + );*/ + //should the status be "reserved"? + _bondDetails.__reservedAmount -= _bondDetails + .__reservedAmountByPurchaseId[_bondPurchaseId]; + _bondDetails.__reservedAmountByAddress[_buyer] -= _bondDetails + .__reservedAmountByPurchaseId[_bondPurchaseId]; + + if (_bondDetails.__reservedAmountByAddress[_buyer] == 0) { + _bondDetails.__investorsCount -= 1; + emit InvestorsCountChanged(_bondId, _bondDetails.__investorsCount); + } + _bondDetails.__revocationsCount += 1; + _bondDetails.__reservedAmountByPurchaseId[_bondPurchaseId] = 0; + emit RevocationsCountChanged(_bondId, _bondDetails.__revocationsCount); + emit ReservedAmountChanged(_bondId, _bondDetails.__reservedAmount); + } + + function issueBond(uint256 _bondId, uint256 _issueDate) external { + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__currentLine = 1; + if (_bondDetails.__issued) { + revert BondAlreadyIssued(); + } + setCouponDatesFromIssueDate(_bondId, _issueDate); + setCouponRates(_bondId); + _bondDetails.__issued = true; + _bondDetails.__status = BondStatus.Issued; + _bondDetails.__issuedAmount = _bondDetails.__reservedAmount; + + emit BondIssued(_bondId, _issueDate, _bondDetails.__issuedAmount); + } + + function withdrawBondsPurchased( + string memory _bondPurchaseId, + uint256 _bondId, + address holder + ) external { + BondParams storage _bondDetails = bondStorage(_bondId); + if (!_bondDetails.__issued) { + revert BondHasNotBeenIssued(); + } + uint256 amount = _bondDetails.__reservedAmountByPurchaseId[ + _bondPurchaseId + ]; + uint256 tokenAmount = amount * _bondDetails.__coupure; + ERC20(__currencyAddress).transferFrom( + holder, + address(this), + tokenAmount + ); + __bond.mint(holder, _bondId, amount); + //_bondDetails.__confirmedReservationByAddress[holder] = 0; + _bondDetails.__isHolder[holder] = true; + emit BondsWithdrawn(_bondPurchaseId, _bondId, holder, amount); + } + + function terminate(uint256 _bondId) external { + BondParams storage _bondDetails = bondStorage(_bondId); + _bondDetails.__status = BondStatus.Terminated; + emit BondTerminated(_bondId); + } + + function transferBond( + string memory _bondTransferId, + uint256 _bondId, + address _old, + address _new, + uint256 _amount + ) external { + BondParams storage _bondDetails = bondStorage(_bondId); + if (!_bondDetails.__issued) { + revert BondHasNotBeenIssued(); + } + if (__bond.balanceOf(_old, _bondId) < _amount) { + revert OldAccountDoesNotHaveEnoughBonds(); + } + uint256 _tokenAmount = _amount * _bondDetails.__coupure; + ERC20(__currencyAddress).transferFrom(_new, _old, _tokenAmount); + __bond.safeTransferFrom(_old, _new, _bondId, _amount, ""); + emit BondTransferred(_bondTransferId, _bondId, _old, _new, _amount); + } + + // claim coupon (+ interest) + function claimCoupon( + uint256 _bondId, + address _buyer + ) external returns (uint256) { + BondParams storage _bondDetails = bondStorage(_bondId); + uint256 userBalance = __bond.balanceOf(_buyer, _bondId); + uint256 interestAmount = convert( + mul( + ud60x18(userBalance), + ud60x18( + _bondDetails.__netCouponRates[_bondDetails.__currentLine] + ) + ) + ); + uint256 capitalAmount = convert( + mul( + ud60x18(userBalance), + ud60x18( + _bondDetails.__capitalRepayment[_bondDetails.__currentLine] + ) + ) + ); + _bondDetails.__nextInterestAmount += userBalance; + _bondDetails.__nextCapitalAmount += userBalance; + + if ( + _bondDetails.__nextCapitalAmount == _bondDetails.__issuedAmount && + _bondDetails.__nextInterestAmount == _bondDetails.__issuedAmount + ) { + _bondDetails.__allClaimsReceived = true; + } + return interestAmount + capitalAmount; + } + + // withdraw coupon (with interest) + function withdrawCouponClaim(uint256 _bondId, address _buyer) external { + BondParams storage _bondDetails = bondStorage(_bondId); + if (!_bondDetails.__allClaimsReceived) { + revert NotAllClaimsReceivedForNextPayment(); + } + + uint256 userBalance = __bond.balanceOf(_buyer, _bondId); + uint256 interestAmount = convert( + mul( + ud60x18(userBalance), + ud60x18( + _bondDetails.__netCouponRates[_bondDetails.__currentLine] + ) + ) + ); + uint256 tokenAmount = userBalance * + _bondDetails.__coupure + + interestAmount; + ERC20(__currencyAddress).transfer(_buyer, tokenAmount); + _bondDetails.__nextInterestAmount -= userBalance; + _bondDetails.__nextCapitalAmount -= userBalance; + + if ( + _bondDetails.__nextInterestAmount == 0 && + _bondDetails.__nextCapitalAmount == 0 + ) { + _bondDetails.__couponStatus[ + _bondDetails.__currentLine + ] = CouponStatus.Executed; + emit CouponStatusChanged(_bondId, _bondDetails.__currentLine); + _bondDetails.__currentLine += 1; + _bondDetails.__allClaimsReceived = false; + } + } + function getSelectors() external pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](20); + selectors[0] = BondFacet.initializeBond.selector; + selectors[1] = BondFacet.setCurrencyAddress.selector; + selectors[2] = BondFacet.editBondParameters.selector; + selectors[3] = BondFacet.cancel.selector; + selectors[4] = BondFacet.setBalloonRate.selector; + selectors[5] = BondFacet.setCapitalAmortizationFreeDuration.selector; + selectors[6] = BondFacet.setGracePeriodDuration.selector; + selectors[7] = BondFacet.getCouponsDates.selector; + selectors[8] = BondFacet.getCouponsRates.selector; + selectors[9] = BondFacet.setInterestRate.selector; + selectors[10] = BondFacet.reserve.selector; + selectors[11] = BondFacet.pauseCampaign.selector; + selectors[12] = BondFacet.unpauseCampaign.selector; + selectors[13] = BondFacet.rescindReservation.selector; + selectors[14] = BondFacet.claimCoupon.selector; + selectors[15] = BondFacet.withdrawCouponClaim.selector; + selectors[16] = BondFacet.transferBond.selector; + selectors[17] = BondFacet.withdrawBondsPurchased.selector; + selectors[18] = BondFacet.terminate.selector; + selectors[19] = BondFacet.issueBond.selector; + + return selectors; + } +} diff --git a/src/facets/BondStorage.sol b/src/facets/BondStorage.sol new file mode 100644 index 0000000..cf1104b --- /dev/null +++ b/src/facets/BondStorage.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract BondStorage { + enum BondStatus { + Unset, + Issued, + Terminated, + Maturity + } + + enum CouponStatus { + Todo, + Executed + } + + enum Periodicity { + Annual, + Quarterly, + Monthly + } + + enum FormOfFinancing { + Bond, + SubordinatedBond + } + + enum MethodOfRepayment { + Bullet, + Degressive, + Balloon, + WithCapitalAmortizationFreePeriod, + GracePeriod, + CapitalAmortizationAndGracePeriod + } + + struct BondParams { + uint256 __campaignMinAmount; + uint256 __campaignMaxAmount; + uint256 __campaignStartDate; + uint256 __campaignEndDate; + uint256 __costEmittent; + uint256 __coupure; + uint256 __interestRate; + uint256 __netReturn; + uint256 __periodicInterestRate; + uint256 __withholdingTax; + uint256 __balloonRate; + uint256 __issueDate; + uint256 __maturityDate; + uint256 __duration; + uint256 __capitalAmortizationDuration; + uint256 __gracePeriodDuration; + uint256 __maxSupply; + uint256 __reservedAmount; + uint256 __maxAmountPerInvestor; + uint256 __previousId; + uint256 __investorsCount; + uint256 __revocationsCount; + uint256 __totalToBeRepaid; + uint256 __issuedAmount; + uint256 __currentLine; + uint256 __nextInterestAmount; + uint256 __nextCapitalAmount; + bool __allClaimsReceived; + bool __isSub; + bool __initDone; + bool __paused; + bool __issued; + bool __cancelled; + uint256[] __grossCouponRates; + uint256[] __couponDates; + uint256[] __netCouponRates; + uint256[] __capitalRepayment; + uint256[] __remainingCapital; + CouponStatus[] __couponStatus; + mapping(address => bool) __isHolder; + mapping(address => uint256) __reservedAmountByAddress; + mapping(string => uint256) __reservedAmountByPurchaseId; + Periodicity __periodicity; + FormOfFinancing __formOfFinancing; + MethodOfRepayment __methodOfRepayment; + BondStatus __status; + address __currencyAddress; + } + + function bondStorage( + uint256 slot + ) internal pure returns (BondParams storage bs) { + bytes32 bsSlot = keccak256( + abi.encodePacked("storage.bond", Strings.toString(slot)) + ); + assembly { + bs.slot := bsSlot + } + } +} diff --git a/src/facets/DiamondCutFacet.sol b/src/facets/DiamondCutFacet.sol new file mode 100644 index 0000000..a398e69 --- /dev/null +++ b/src/facets/DiamondCutFacet.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * \ + * Author: Nick Mudge (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"; + +// Remember to add the loupe functions from DiamondLoupeFacet to the diamond. +// The loupe functions are required by the EIP2535 Diamonds standard + +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, bytes calldata _calldata) external override { + LibDiamond.enforceIsContractOwner(); + LibDiamond.diamondCut(_diamondCut, _init, _calldata); + } +} diff --git a/src/facets/DiamondLoupeFacet.sol b/src/facets/DiamondLoupeFacet.sol new file mode 100644 index 0000000..b1a624a --- /dev/null +++ b/src/facets/DiamondLoupeFacet.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +/** + * \ + * Author: Nick Mudge (https://twitter.com/mudgen) + * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 + * /***************************************************************************** + */ + +// The functions in DiamondLoupeFacet MUST be added to a diamond. +// The EIP-2535 Diamond standard requires these functions. + +import {LibDiamond} from "../libraries/LibDiamond.sol"; +import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol"; +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; + +contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { + // Diamond Loupe Functions + //////////////////////////////////////////////////////////////////// + /// These functions are expected to be called frequently by tools. + // + // struct Facet { + // address facetAddress; + // bytes4[] functionSelectors; + // } + /// @notice Gets all facets and their selectors. + /// @return facets_ Facet + function facets() external view override returns (Facet[] memory facets_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 selectorCount = ds.selectors.length; + // create an array set to the maximum size possible + facets_ = new Facet[](selectorCount); + // create an array for counting the number of selectors for each facet + uint8[] memory numFacetSelectors = new uint8[](selectorCount); + // total number of facets + uint256 numFacets; + // loop through function selectors + for ( + uint256 selectorIndex; + selectorIndex < selectorCount; + selectorIndex++ + ) { + bytes4 selector = ds.selectors[selectorIndex]; + address facetAddress_ = ds + .facetAddressAndSelectorPosition[selector] + .facetAddress; + bool continueLoop = false; + // find the functionSelectors array for selector and add selector to it + for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { + if (facets_[facetIndex].facetAddress == facetAddress_) { + facets_[facetIndex].functionSelectors[ + numFacetSelectors[facetIndex] + ] = selector; + // probably will never have more than 256 functions from one facet contract + require( + numFacetSelectors[facetIndex] < 255, + "amount of function has to be less than 255" + ); + numFacetSelectors[facetIndex]++; + continueLoop = true; + break; + } + } + // if functionSelectors array exists for selector then continue loop + if (continueLoop) { + continueLoop = false; + continue; + } + // create a new functionSelectors array for selector + facets_[numFacets].facetAddress = facetAddress_; + facets_[numFacets].functionSelectors = new bytes4[](selectorCount); + facets_[numFacets].functionSelectors[0] = selector; + numFacetSelectors[numFacets] = 1; + numFacets++; + } + for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { + uint256 numSelectors = numFacetSelectors[facetIndex]; + bytes4[] memory selectors = facets_[facetIndex].functionSelectors; + // setting the number of selectors + assembly { + mstore(selectors, numSelectors) + } + } + // setting the number of facets + assembly { + mstore(facets_, numFacets) + } + } + + /// @notice Gets all the function selectors supported by a specific facet. + /// @param _facet The facet address. + /// @return _facetFunctionSelectors The selectors associated with a facet address. + function facetFunctionSelectors( + address _facet + ) external view override returns (bytes4[] memory _facetFunctionSelectors) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 selectorCount = ds.selectors.length; + uint256 numSelectors; + _facetFunctionSelectors = new bytes4[](selectorCount); + // loop through function selectors + for ( + uint256 selectorIndex; + selectorIndex < selectorCount; + selectorIndex++ + ) { + bytes4 selector = ds.selectors[selectorIndex]; + address facetAddress_ = ds + .facetAddressAndSelectorPosition[selector] + .facetAddress; + if (_facet == facetAddress_) { + _facetFunctionSelectors[numSelectors] = selector; + numSelectors++; + } + } + // Set the number of selectors in the array + assembly { + mstore(_facetFunctionSelectors, numSelectors) + } + } + + /// @notice Get all the facet addresses used by a diamond. + /// @return facetAddresses_ + function facetAddresses() + external + view + override + returns (address[] memory facetAddresses_) + { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 selectorCount = ds.selectors.length; + // create an array set to the maximum size possible + facetAddresses_ = new address[](selectorCount); + uint256 numFacets; + // loop through function selectors + for ( + uint256 selectorIndex; + selectorIndex < selectorCount; + selectorIndex++ + ) { + bytes4 selector = ds.selectors[selectorIndex]; + address facetAddress_ = ds + .facetAddressAndSelectorPosition[selector] + .facetAddress; + bool continueLoop = false; + // see if we have collected the address already and break out of loop if we have + for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { + if (facetAddress_ == facetAddresses_[facetIndex]) { + continueLoop = true; + break; + } + } + // continue loop if we already have the address + if (continueLoop) { + continueLoop = false; + continue; + } + // include address + facetAddresses_[numFacets] = facetAddress_; + numFacets++; + } + // Set the number of facet addresses in the array + assembly { + mstore(facetAddresses_, numFacets) + } + } + + /// @notice Gets the facet address that supports the given selector. + /// @dev If facet is not found return address(0). + /// @param _functionSelector The function selector. + /// @return facetAddress_ The facet address. + function facetAddress( + bytes4 _functionSelector + ) external view override returns (address facetAddress_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddress_ = ds + .facetAddressAndSelectorPosition[_functionSelector] + .facetAddress; + } + + // This implements ERC-165. + function supportsInterface( + bytes4 _interfaceId + ) external view override returns (bool) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + return ds.supportedInterfaces[_interfaceId]; + } + + function getSelectors() external pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = DiamondLoupeFacet.facets.selector; + selectors[1] = DiamondLoupeFacet.facetAddress.selector; + selectors[2] = DiamondLoupeFacet.facetFunctionSelectors.selector; + selectors[3] = DiamondLoupeFacet.facetAddresses.selector; + + return selectors; + } +} diff --git a/src/facets/ERC1155Facet.sol b/src/facets/ERC1155Facet.sol new file mode 100644 index 0000000..a6ef64f --- /dev/null +++ b/src/facets/ERC1155Facet.sol @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +//import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +//import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +//import "../interfaces/IERC1155.sol"; +//import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC1155v2} from "../interfaces/IERC1155v2.sol"; + +contract ERC1155Facet is Context, ERC165, IERC1155v2 { + using Address for address; + + // Mapping from token ID to account balances + mapping(uint256 => mapping(address => uint256)) private _balances; + + // Mapping from account to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json + //string private _uri; + + /** + * @dev See {_setURI}. + */ + constructor() { + // _setURI(uri_); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC1155v2).interfaceId || + //interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `\{id\}` substring with the + * actual token type ID. + */ + /*function uri(uint256) public view virtual override returns (string memory) { + return _uri; + }*/ + + /** + * @dev See {IERC1155-balanceOf}. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf( + address account, + uint256 id + ) public view virtual override returns (uint256) { + require( + account != address(0), + "ERC1155: balance query for the zero address" + ); + return _balances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) public view virtual override returns (uint256[] memory) { + require( + accounts.length == ids.length, + "ERC1155: accounts and ids length mismatch" + ); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll( + address owner, + address operator, + bool approved + ) public virtual override { + _setApprovalForAll(owner, operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll( + address account, + address operator + ) public view virtual override returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + //require(from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not owner nor + // approved"); + _safeTransferFrom(from, to, id, amount, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + /*require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: transfer caller is not owner nor approved" + );*/ + _safeBatchTransferFrom(from, to, ids, amounts, data); + } + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + //_beforeTokenTransfer(operator, from, to, ids, amounts, data); + + uint256 fromBalance = _balances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + + emit TransferSingle(operator, from, to, id, amount); + + //_afterTokenTransfer(operator, from, to, ids, amounts, data); + + //_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + //_beforeTokenTransfer(operator, from, to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + + //_afterTokenTransfer(operator, from, to, ids, amounts, data); + + //_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * By this mechanism, any occurrence of the `\{id\}` substring in either the + * URI or any of the amounts in the JSON file at said URI will be replaced by + * clients with the token type ID. + * + * For example, the `https://token-cdn-domain/\{id\}.json` URI would be + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + /*function _setURI(string memory newuri) internal virtual { + _uri = newuri; + }*/ + + /** + * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function getmsgSender() external view returns (address) { + return _msgSender(); + } + + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + //require(to != address(0), "ERC1155: mint to the zero address"); + //require(bytes(to).length != 0, "ERC1155: mint to invalid account"); + + address operator = _msgSender(); + + //_beforeTokenTransfer(operator, "", to, ids, amounts, data); + + _balances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + //_afterTokenTransfer(operator, "", to, ids, amounts, data); + + //_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + } + + /** + * @dev Destroys `amount` tokens of token type `id` from `from` + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `amount` tokens of token type `id`. + */ + function _burn(address from, uint256 id, uint256 amount) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = _msgSender(); + + //_beforeTokenTransfer(operator, from, "", ids, amounts, ""); + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + + //_afterTokenTransfer(operator, from, "", ids, amounts, ""); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits a {ApprovalForAll} event. + */ + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + //require(owner != operator, "ERC1155: setting approval status for self"); + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + function mint(address to, uint256 id, uint256 amount) external { + _mint(to, id, amount, ""); + } + + function burn(address from, uint256 id, uint256 amount) external { + _burn(from, id, amount); + } + + function getSelectors() external pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = ERC1155Facet.mint.selector; + selectors[1] = ERC1155Facet.burn.selector; + selectors[2] = ERC1155Facet.getmsgSender.selector; + + return selectors; + } +} diff --git a/src/interfaces/IDiamond.sol b/src/interfaces/IDiamond.sol new file mode 100644 index 0000000..76b1668 --- /dev/null +++ b/src/interfaces/IDiamond.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//*****************************************************************************\ +//* Author: Nick Mudge (https://twitter.com/mudgen) +//* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +//******************************************************************************/ + +interface IDiamond { + enum FacetCutAction { + Add, + Replace, + Remove + } + // Add=0, Replace=1, Remove=2 + + struct FacetCut { + address facetAddress; + FacetCutAction action; + bytes4[] functionSelectors; + } + + event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); +} diff --git a/src/interfaces/IDiamondCut.sol b/src/interfaces/IDiamondCut.sol new file mode 100644 index 0000000..44e2c69 --- /dev/null +++ b/src/interfaces/IDiamondCut.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//******************************************************************************\ +//* Author: Nick Mudge (https://twitter.com/mudgen) +//* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +//******************************************************************************/ + +import {IDiamond} from "./IDiamond.sol"; + +interface IDiamondCut is IDiamond { + /// @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, + bytes calldata _calldata + ) external; +} diff --git a/src/interfaces/IDiamondLoupe.sol b/src/interfaces/IDiamondLoupe.sol new file mode 100644 index 0000000..d25f5a0 --- /dev/null +++ b/src/interfaces/IDiamondLoupe.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//******************************************************************************\ +//* Author: Nick Mudge (https://twitter.com/mudgen) +//* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +//******************************************************************************/ + +// A loupe is a small magnifying glass used to look at diamonds. +// These functions look at diamonds +interface IDiamondLoupe { + /// These functions are expected to be called frequently + /// by tools. + + struct Facet { + address facetAddress; + bytes4[] functionSelectors; + } + + /// @notice Gets all facet addresses and their four byte function selectors. + /// @return facets_ Facet + function facets() external view returns (Facet[] memory facets_); + + /// @notice Gets all the function selectors supported by a specific facet. + /// @param _facet The facet address. + /// @return facetFunctionSelectors_ + function facetFunctionSelectors( + address _facet + ) external view returns (bytes4[] memory facetFunctionSelectors_); + + /// @notice Get all the facet addresses used by a diamond. + /// @return facetAddresses_ + function facetAddresses() + external + view + returns (address[] memory facetAddresses_); + + /// @notice Gets the facet that supports the given selector. + /// @dev If facet is not found return address(0). + /// @param _functionSelector The function selector. + /// @return facetAddress_ The facet address. + function facetAddress( + bytes4 _functionSelector + ) external view returns (address facetAddress_); +} diff --git a/src/interfaces/IERC1155v2.sol b/src/interfaces/IERC1155v2.sol new file mode 100644 index 0000000..8fe2d34 --- /dev/null +++ b/src/interfaces/IERC1155v2.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/IERC1155.sol) + +pragma solidity ^0.8.0; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC1155 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1155[EIP]. + * + * _Available since v3.1._ + */ +interface IERC1155v2 is IERC165 { + /** + * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the amount of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch( + address[] calldata accounts, + uint256[] calldata ids + ) + external + view + returns (uint256[] memory); + + /** + * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address owner, address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) + external; +} diff --git a/src/interfaces/IERC173.sol b/src/interfaces/IERC173.sol new file mode 100644 index 0000000..90b73cc --- /dev/null +++ b/src/interfaces/IERC173.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ERC-173 Contract Ownership Standard +/// Note: the ERC-165 identifier for this interface is 0x7f5828d0 +/* is ERC165 */ +interface IERC173 { + /// @dev This emits when ownership of a contract changes. + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /// @notice Get the address of the owner + /// @return owner_ The address of the owner. + function owner() external view returns (address owner_); + + /// @notice Set the address of the new owner of the contract + /// @dev Set _newOwner to address(0) to renounce any ownership. + /// @param _newOwner The address of the new owner of the contract + function transferOwnership(address _newOwner) external; +} diff --git a/src/libraries/BokkyPooBahsDateTimeLibrary.sol b/src/libraries/BokkyPooBahsDateTimeLibrary.sol new file mode 100644 index 0000000..271cfe8 --- /dev/null +++ b/src/libraries/BokkyPooBahsDateTimeLibrary.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +// ---------------------------------------------------------------------------- +// BokkyPooBah's DateTime Library v1.01 +// +// A gas-efficient Solidity date and time library +// +// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary +// +// Tested date range 1970/01/01 to 2345/12/31 +// +// Conventions: +// Unit | Range | Notes +// :-------- |:-------------:|:----- +// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC +// year | 1970 ... 2345 | +// month | 1 ... 12 | +// day | 1 ... 31 | +// hour | 0 ... 23 | +// minute | 0 ... 59 | +// second | 0 ... 59 | +// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday +// +// +// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence. +// ---------------------------------------------------------------------------- + +library BokkyPooBahsDateTimeLibrary { + uint256 internal constant SECONDS_PER_DAY = 24 * 60 * 60; + uint256 internal constant SECONDS_PER_HOUR = 60 * 60; + uint256 internal constant SECONDS_PER_MINUTE = 60; + int256 internal constant OFFSET19700101 = 2_440_588; + + uint256 internal constant DOW_MON = 1; + uint256 internal constant DOW_TUE = 2; + uint256 internal constant DOW_WED = 3; + uint256 internal constant DOW_THU = 4; + uint256 internal constant DOW_FRI = 5; + uint256 internal constant DOW_SAT = 6; + uint256 internal constant DOW_SUN = 7; + + // ------------------------------------------------------------------------ + // Calculate the number of days from 1970/01/01 to year/month/day using + // the date conversion algorithm from + // https://aa.usno.navy.mil/faq/JD_formula.html + // and subtracting the offset 2440588 so that 1970/01/01 is day 0 + // + // days = day + // - 32075 + // + 1461 * (year + 4800 + (month - 14) / 12) / 4 + // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 + // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 + // - offset + // ------------------------------------------------------------------------ + function _daysFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 _days) { + require(year >= 1970, "Year cannot be earlier than 1970"); + int256 _year = int256(year); + int256 _month = int256(month); + int256 _day = int256(day); + + int256 __days = _day - 32_075 + (1461 * (_year + 4800 + (_month - 14) / 12)) / 4 + + (367 * (_month - 2 - ((_month - 14) / 12) * 12)) / 12 - (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) / 4 + - OFFSET19700101; + + _days = uint256(__days); + } + + // ------------------------------------------------------------------------ + // Calculate year/month/day from the number of days since 1970/01/01 using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and adding the offset 2440588 so that 1970/01/01 is day 0 + // ------------------------------------------------------------------------ + function _daysToDate(uint256 _days) internal pure returns (uint256 year, uint256 month, uint256 day) { + int256 __days = int256(_days); + + int256 length = __days + 68_569 + OFFSET19700101; + int256 n = (4 * length) / 146_097; + length = length - (146_097 * n + 3) / 4; + int256 _year = (4000 * (length + 1)) / 1_461_001; + length = length - (1461 * _year) / 4 + 31; + int256 _month = (80 * length) / 2447; + int256 _day = length - (2447 * _month) / 80; + length = _month / 11; + _month = _month + 2 - 12 * length; + _year = 100 * (n - 49) + _year + length; + + year = uint256(_year); + month = uint256(_month); + day = uint256(_day); + } + + function timestampFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 timestamp) { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; + } + + function timestampFromDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) + internal + pure + returns (uint256 timestamp) + { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + hour * SECONDS_PER_HOUR + + minute * SECONDS_PER_MINUTE + second; + } + + function timestampToDate(uint256 timestamp) internal pure returns (uint256 year, uint256 month, uint256 day) { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + + function timestampToDateTime(uint256 timestamp) + internal + pure + returns (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) + { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + second = secs % SECONDS_PER_MINUTE; + } + + function isValidDate(uint256 year, uint256 month, uint256 day) internal pure returns (bool valid) { + if (year >= 1970 && month > 0 && month <= 12) { + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > 0 && day <= daysInMonth) { + valid = true; + } + } + } + + function isValidDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) + internal + pure + returns (bool valid) + { + if (isValidDate(year, month, day)) { + if (hour < 24 && minute < 60 && second < 60) { + valid = true; + } + } + } + + function isLeapYear(uint256 timestamp) internal pure returns (bool leapYear) { + (uint256 year,,) = _daysToDate(timestamp / SECONDS_PER_DAY); + leapYear = _isLeapYear(year); + } + + function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { + leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); + } + + function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { + weekDay = getDayOfWeek(timestamp) <= DOW_FRI; + } + + function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { + weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; + } + + function getDaysInMonth(uint256 timestamp) internal pure returns (uint256 daysInMonth) { + (uint256 year, uint256 month,) = _daysToDate(timestamp / SECONDS_PER_DAY); + daysInMonth = _getDaysInMonth(year, month); + } + + function _getDaysInMonth(uint256 year, uint256 month) internal pure returns (uint256 daysInMonth) { + if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { + daysInMonth = 31; + } else if (month != 2) { + daysInMonth = 30; + } else { + daysInMonth = _isLeapYear(year) ? 29 : 28; + } + } + + // 1 = Monday, 7 = Sunday + function getDayOfWeek(uint256 timestamp) internal pure returns (uint256 dayOfWeek) { + uint256 _days = timestamp / SECONDS_PER_DAY; + dayOfWeek = ((_days + 3) % 7) + 1; + } + + function getYear(uint256 timestamp) internal pure returns (uint256 year) { + (year,,) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + + function getMonth(uint256 timestamp) internal pure returns (uint256 month) { + (, month,) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + + function getDay(uint256 timestamp) internal pure returns (uint256 day) { + (,, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + + function getHour(uint256 timestamp) internal pure returns (uint256 hour) { + uint256 secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + } + + function getMinute(uint256 timestamp) internal pure returns (uint256 minute) { + uint256 secs = timestamp % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + } + + function getSecond(uint256 timestamp) internal pure returns (uint256 second) { + second = timestamp % SECONDS_PER_MINUTE; + } + + function addYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); + year += _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function addMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); + month += _months; + year += (month - 1) / 12; + month = ((month - 1) % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function addDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp + _days * SECONDS_PER_DAY; + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function addHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function addMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function addSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp + _seconds; + require(newTimestamp >= timestamp, "Timestamp should be earlier or equal to the current timestamp"); + } + + function subYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); + year -= _years; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function subMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint256 yearMonth = year * 12 + (month - 1) - _months; + year = yearMonth / 12; + month = (yearMonth % 12) + 1; + uint256 daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function subDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp - _days * SECONDS_PER_DAY; + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function subHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function subMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function subSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) { + newTimestamp = timestamp - _seconds; + require(newTimestamp <= timestamp, "New timestamp should be later or equal to the current timestamp"); + } + + function diffYears(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _years) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + (uint256 fromYear,,) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear,,) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _years = toYear - fromYear; + } + + function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _months) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + (uint256 fromYear, uint256 fromMonth,) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear, uint256 toMonth,) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; + } + + function diffDays(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _days) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; + } + + function diffHours(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _hours) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; + } + + function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _minutes) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; + } + + function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _seconds) { + require(fromTimestamp <= toTimestamp, "New timestamp should be later or equal to the current timestamp"); + _seconds = toTimestamp - fromTimestamp; + } +} diff --git a/src/libraries/LibDiamond.sol b/src/libraries/LibDiamond.sol new file mode 100644 index 0000000..d3645f4 --- /dev/null +++ b/src/libraries/LibDiamond.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//******************************************************************************\ +//* Author: Nick Mudge (https://twitter.com/mudgen) +//* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +//******************************************************************************/ +import {IDiamond} from "../interfaces/IDiamond.sol"; +import {IDiamondCut} from "../interfaces/IDiamondCut.sol"; + +// Remember to add the loupe functions from DiamondLoupeFacet to the diamond. +// The loupe functions are required by the EIP2535 Diamonds standard + +error NoSelectorsGivenToAdd(); +error NotContractOwner(address _user, address _contractOwner); +error NoSelectorsProvidedForFacetForCut(address _facetAddress); +error CannotAddSelectorsToZeroAddress(bytes4[] _selectors); +error NoBytecodeAtAddress(address _contractAddress, string _message); +error IncorrectFacetCutAction(uint8 _action); +error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); +error CannotReplaceFunctionsFromFacetWithZeroAddress(bytes4[] _selectors); +error CannotReplaceImmutableFunction(bytes4 _selector); +error CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet( + bytes4 _selector +); +error CannotReplaceFunctionThatDoesNotExists(bytes4 _selector); +error RemoveFacetAddressMustBeZeroAddress(address _facetAddress); +error CannotRemoveFunctionThatDoesNotExist(bytes4 _selector); +error CannotRemoveImmutableFunction(bytes4 _selector); +error InitializationFunctionReverted( + address _initializationContractAddress, + bytes _calldata +); + +library LibDiamond { + bytes32 constant DIAMOND_STORAGE_POSITION = + keccak256("diamond.standard.diamond.storage"); + + struct FacetAddressAndSelectorPosition { + address facetAddress; + uint16 selectorPosition; + } + + struct DiamondStorage { + // function selector => facet address and selector position in selectors array + mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; + bytes4[] selectors; + mapping(bytes4 => bool) supportedInterfaces; + // owner of the contract + address contractOwner; + } + + function diamondStorage() + internal + pure + returns (DiamondStorage storage ds) + { + bytes32 position = DIAMOND_STORAGE_POSITION; + assembly { + ds.slot := position + } + } + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + function setContractOwner(address _newOwner) internal { + DiamondStorage storage ds = diamondStorage(); + address previousOwner = ds.contractOwner; + ds.contractOwner = _newOwner; + emit OwnershipTransferred(previousOwner, _newOwner); + } + + function contractOwner() internal view returns (address contractOwner_) { + contractOwner_ = diamondStorage().contractOwner; + } + + function enforceIsContractOwner() internal view { + if (msg.sender != diamondStorage().contractOwner) { + revert NotContractOwner(msg.sender, diamondStorage().contractOwner); + } + } + + event DiamondCut( + IDiamondCut.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); + + // Internal function version of diamondCut + function diamondCut( + IDiamondCut.FacetCut[] memory _diamondCut, + address _init, + bytes memory _calldata + ) internal { + for ( + uint256 facetIndex; + facetIndex < _diamondCut.length; + facetIndex++ + ) { + bytes4[] memory functionSelectors = _diamondCut[facetIndex] + .functionSelectors; + address facetAddress = _diamondCut[facetIndex].facetAddress; + if (functionSelectors.length == 0) { + revert NoSelectorsProvidedForFacetForCut(facetAddress); + } + IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; + if (action == IDiamond.FacetCutAction.Add) { + addFunctions(facetAddress, functionSelectors); + } else if (action == IDiamond.FacetCutAction.Replace) { + replaceFunctions(facetAddress, functionSelectors); + } else if (action == IDiamond.FacetCutAction.Remove) { + removeFunctions(facetAddress, functionSelectors); + } else { + revert IncorrectFacetCutAction(uint8(action)); + } + } + emit DiamondCut(_diamondCut, _init, _calldata); + initializeDiamondCut(_init, _calldata); + } + + function addFunctions( + address _facetAddress, + bytes4[] memory _functionSelectors + ) internal { + if (_facetAddress == address(0)) { + revert CannotAddSelectorsToZeroAddress(_functionSelectors); + } + DiamondStorage storage ds = diamondStorage(); + uint16 selectorCount = uint16(ds.selectors.length); + enforceHasContractCode( + _facetAddress, + "LibDiamondCut: Add facet has no code" + ); + for ( + uint256 selectorIndex; + selectorIndex < _functionSelectors.length; + selectorIndex++ + ) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacetAddress = ds + .facetAddressAndSelectorPosition[selector] + .facetAddress; + if (oldFacetAddress != address(0)) { + revert CannotAddFunctionToDiamondThatAlreadyExists(selector); + } + ds.facetAddressAndSelectorPosition[ + selector + ] = FacetAddressAndSelectorPosition(_facetAddress, selectorCount); + ds.selectors.push(selector); + selectorCount++; + } + } + + function replaceFunctions( + address _facetAddress, + bytes4[] memory _functionSelectors + ) internal { + DiamondStorage storage ds = diamondStorage(); + if (_facetAddress == address(0)) { + revert CannotReplaceFunctionsFromFacetWithZeroAddress( + _functionSelectors + ); + } + enforceHasContractCode( + _facetAddress, + "LibDiamondCut: Replace facet has no code" + ); + for ( + uint256 selectorIndex; + selectorIndex < _functionSelectors.length; + selectorIndex++ + ) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacetAddress = ds + .facetAddressAndSelectorPosition[selector] + .facetAddress; + // can't replace immutable functions -- functions defined directly in the diamond in this case + if (oldFacetAddress == address(this)) { + revert CannotReplaceImmutableFunction(selector); + } + if (oldFacetAddress == _facetAddress) { + revert CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet( + selector + ); + } + if (oldFacetAddress == address(0)) { + revert CannotReplaceFunctionThatDoesNotExists(selector); + } + // replace old facet address + ds + .facetAddressAndSelectorPosition[selector] + .facetAddress = _facetAddress; + } + } + + function removeFunctions( + address _facetAddress, + bytes4[] memory _functionSelectors + ) internal { + DiamondStorage storage ds = diamondStorage(); + uint256 selectorCount = ds.selectors.length; + if (_facetAddress != address(0)) { + revert RemoveFacetAddressMustBeZeroAddress(_facetAddress); + } + for ( + uint256 selectorIndex; + selectorIndex < _functionSelectors.length; + selectorIndex++ + ) { + bytes4 selector = _functionSelectors[selectorIndex]; + FacetAddressAndSelectorPosition + memory oldFacetAddressAndSelectorPosition = ds + .facetAddressAndSelectorPosition[selector]; + if (oldFacetAddressAndSelectorPosition.facetAddress == address(0)) { + revert CannotRemoveFunctionThatDoesNotExist(selector); + } + + // can't remove immutable functions -- functions defined directly in the diamond + if ( + oldFacetAddressAndSelectorPosition.facetAddress == address(this) + ) { + revert CannotRemoveImmutableFunction(selector); + } + // replace selector with last selector + selectorCount--; + if ( + oldFacetAddressAndSelectorPosition.selectorPosition != + selectorCount + ) { + bytes4 lastSelector = ds.selectors[selectorCount]; + ds.selectors[ + oldFacetAddressAndSelectorPosition.selectorPosition + ] = lastSelector; + ds + .facetAddressAndSelectorPosition[lastSelector] + .selectorPosition = oldFacetAddressAndSelectorPosition + .selectorPosition; + } + // delete last selector + ds.selectors.pop(); + delete ds.facetAddressAndSelectorPosition[selector]; + } + } + + function initializeDiamondCut( + address _init, + bytes memory _calldata + ) internal { + if (_init == address(0)) { + return; + } + enforceHasContractCode( + _init, + "LibDiamondCut: _init address has no code" + ); + (bool success, bytes memory error) = _init.delegatecall(_calldata); + if (!success) { + if (error.length > 0) { + // bubble up error + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(error) + revert(add(32, error), returndata_size) + } + } else { + revert InitializationFunctionReverted(_init, _calldata); + } + } + } + + function enforceHasContractCode( + address _contract, + string memory _errorMessage + ) internal view { + uint256 contractSize; + assembly { + contractSize := extcodesize(_contract) + } + if (contractSize == 0) { + revert NoBytecodeAtAddress(_contract, _errorMessage); + } + } +} diff --git a/src/libraries/StructBondInit.sol b/src/libraries/StructBondInit.sol new file mode 100644 index 0000000..9f210d9 --- /dev/null +++ b/src/libraries/StructBondInit.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +library BondInitParams { + struct BondInit { + uint256 __bondId; + uint256 __campaignMinAmount; + uint256 __campaignMaxAmount; + uint256 __campaignStartDate; + uint256 __expectedIssueDate; + uint256 __coupure; + uint256 __interestNum; + uint256 __interestDen; + uint256 __withholdingTaxNum; + uint256 __withholdingTaxDen; + uint256 __balloonRateNum; + uint256 __balloonRateDen; + uint256 __duration; + uint256 __capitalAmortizationDuration; + uint256 __gracePeriodDuration; + uint256 __maxAmountPerInvestor; + uint256 __periodicity; + uint256 __formOfFinancing; + uint256 __methodOfRepayment; + } +} diff --git a/src/libraries/structClaim.sol b/src/libraries/structClaim.sol new file mode 100644 index 0000000..8807429 --- /dev/null +++ b/src/libraries/structClaim.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +library ClaimParams { + struct Claim { + string claimId; + uint256 bondId; + uint256 amount; + bytes32 hashLockWithdrawPayment; + uint256 withdrawPaymentTimestampEnd; + address beneficiary; + } +} diff --git a/src/upgradeInitializers/DiamondInit.sol b/src/upgradeInitializers/DiamondInit.sol new file mode 100644 index 0000000..16e5b02 --- /dev/null +++ b/src/upgradeInitializers/DiamondInit.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/******************************************************************************\ +* Author: Nick Mudge (https://twitter.com/mudgen) +* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 +* +* Implementation of a diamond. +/******************************************************************************/ + +import {LibDiamond} from "../libraries/LibDiamond.sol"; +import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol"; +import {IDiamondCut} from "../interfaces/IDiamondCut.sol"; +import {IERC173} from "../interfaces/IERC173.sol"; +//import { IERC165 } from "../interfaces/IERC165.sol"; +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; + +// It is expected that this contract is customized if you want to deploy your diamond +// with data from a deployment script. Use the init function to initialize state variables +// of your diamond. Add parameters to the init funciton if you need to. + +contract DiamondInit { + // You can add parameters to this function in order to pass in + // data to set your own state variables + function init() external { + // adding ERC165 data + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + ds.supportedInterfaces[type(IERC165).interfaceId] = true; + ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; + ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; + ds.supportedInterfaces[type(IERC173).interfaceId] = true; + + // add your own state variables + // EIP-2535 specifies that the `diamondCut` function takes two optional + // arguments: address _init and bytes calldata _calldata + // These arguments are used to execute an arbitrary function using delegatecall + // in order to set state variables in the diamond during deployment or an upgrade + // More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface + } +} diff --git a/subgraph/datasources/diamond.gql.json b/subgraph/datasources/diamond.gql.json new file mode 100755 index 0000000..2b37cb0 --- /dev/null +++ b/subgraph/datasources/diamond.gql.json @@ -0,0 +1,429 @@ +[ + { + "name": "Account", + "fields": [ + { "name": "id", "type": "Bytes!" }, + { "name": "asBondFacet", "type": "BondFacet" }, + { "name": "BondParametersEditedEvents", "type": "BondParametersEdited!", "derived": "emitter" }, + { "name": "CouponsComputedEvents", "type": "CouponsComputed!", "derived": "emitter" }, + { "name": "BondsWithdrawnEvents", "type": "BondsWithdrawn!", "derived": "emitter" }, + { "name": "BondIssuedEvents", "type": "BondIssued!", "derived": "emitter" }, + { "name": "BalloonRateSetEvents", "type": "BalloonRateSet!", "derived": "emitter" }, + { "name": "GracePeriodSetEvents", "type": "GracePeriodSet!", "derived": "emitter" }, + { "name": "CostEmittentSetEvents", "type": "CostEmittentSet!", "derived": "emitter" }, + { + "name": "CapitalAmortizationFreePeriodSetEvents", + "type": "CapitalAmortizationFreePeriodSet!", + "derived": "emitter" + }, + { "name": "InvestorsCountChangedEvents", "type": "InvestorsCountChanged!", "derived": "emitter" }, + { "name": "RevocationsCountChangedEvents", "type": "RevocationsCountChanged!", "derived": "emitter" }, + { "name": "CampaignStartAndEndDateSetEvents", "type": "CampaignStartAndEndDateSet!", "derived": "emitter" }, + { "name": "CampaignPausedEvents", "type": "CampaignPaused!", "derived": "emitter" }, + { "name": "CampaignUnpausedEvents", "type": "CampaignUnpaused!", "derived": "emitter" }, + { "name": "MinAndMaxAmountSetEvents", "type": "MinAndMaxAmountSet!", "derived": "emitter" }, + { "name": "BondTransferrerdEvents", "type": "BondTransferred!", "derived": "emitter" }, + { "name": "ReservedAmountChangedEvents", "type": "ReservedAmountChanged!", "derived": "emitter" }, + { "name": "CouponStatusChangedEvents", "type": "CouponStatusChanged!", "derived": "emitter"} + ] + }, + { + "name": "BondFacet", + "fields": [ + { "name": "asAccount", "type": "Account" }, + { "name": "BondInitializedEvent", "type": "BondInitialized!", "derived": "contract" }, + { "name": "BondParametersEditedEvent", "type": "BondParametersEdited!", "derived": "contract" }, + { "name": "CouponsComputedEvent", "type": "CouponsComputed!", "derived": "contract" }, + { "name": "BondsWithdrawn", "type": "BondsWithdrawn!", "derived": "contract" }, + { "name": "BondIssued", "type": "BondIssued!", "derived": "contract" }, + { "name": "BalloonRateSet", "type": "BalloonRateSet!", "derived": "contract" }, + { "name": "GracePeriodSet", "type": "GracePeriodSet!", "derived": "contract" }, + { + "name": "CapitalAmortizationFreePeriodSet", + "type": "CapitalAmortizationFreePeriodSet!", + "derived": "contract" + }, + { "name": "CostEmittentSet", "type": "CostEmittentSet!", "derived": "contract" }, + { "name": "InvestorsCountChanged", "type": "InvestorsCountChanged!", "derived": "contract" }, + { "name": "RevocationsCountChanged", "type": "RevocationsCountChanged!", "derived": "contract" }, + { "name": "CampaignStartAndEndDateSet", "type": "CampaignStartAndEndDateSet!", "derived": "contract" }, + { "name": "CampaignPaused", "type": "CampaignPaused!", "derived": "contract" }, + { "name": "CampaignUnpaused", "type": "CampaignUnpaused!", "derived": "contract" }, + { "name": "MinAndMaxAmountSet", "type": "MinAndMaxAmountSet!", "derived": "contract" }, + { "name": "IssueDateSet", "type": "IssueDateSet!", "derived": "contract" }, + { "name": "BondTransferred", "type": "BondTransferred!", "derived": "contract" }, + { "name": "ReservedAmountChanged", "type": "ReservedAmountChanged!", "derived": "contract" }, + { "name": "CouponStatusChanged", "type": "CouponStatusChanged!", "derived": "contract"} + ] + }, + { + "name": "Bond", + "fields": [ + { "name": "contract", "type": "BondFacet!" }, + { "name": "coupure", "type": "BigInt!" }, + { "name": "grossInterestRate", "type": "BigDecimal!" }, + { "name": "netReturn", "type": "BigDecimal!" }, + { "name": "withholdingTaxRate", "type": "BigDecimal!" }, + { "name": "periodicInterestRate", "type": "BigDecimal!" }, + { "name": "holders", "type": "[Account!]!" }, + { "name": "holdersAmount", "type": "[BigInt!]!" }, + { "name": "reservationsByAddresses", "type": "[Account!]!" }, + { "name": "reservedAmountByAddresses", "type": "[Account!]!" }, + { "name": "reservedAmount", "type": "BigInt!" }, + { "name": "periodicity", "type": "String!" }, + { "name": "methodOfRepayment", "type": "String!" }, + { "name": "duration", "type": "BigInt!" }, + { "name": "gracePeriod", "type": "BigInt!" }, + { "name": "balloonPercentage", "type": "BigDecimal!" }, + { "name": "capitalAmortizationFreePeriod", "type": "BigInt!" }, + { "name": "costEmittent", "type": "BigDecimal!" }, + { "name": "investorsCount", "type": "BigInt!" }, + { "name": "revocationsCount", "type": "BigInt!" }, + { "name": "campaignStartDate", "type": "BigInt!" }, + { "name": "campaignEndDate", "type": "BigInt!" }, + { "name": "paused", "type": "Boolean!" }, + { "name": "maxAmountPerInvestor", "type": "BigDecimal!" }, + { "name": "maxSupply", "type": "BigDecimal!" }, + { "name": "maxAmount", "type": "BigInt!" }, + { "name": "minAmount", "type": "BigInt!" }, + { "name": "issueDate", "type": "BigInt!" }, + { "name": "formOfFinancing", "type": "String!" }, + { "name": "status", "type": "String!" }, + { "name": "cancelRef", "type": "String" }, + { "name": "withdrawRef", "type": "String" }, + { "name": "withdrawStartTime", "type": "BigInt!" }, + { "name": "withdrawEndTime", "type": "BigInt!" }, + { "name": "cancelStartTime", "type": "BigInt!" }, + { "name": "cancelEndTime", "type": "BigInt!" }, + { "name": "hashLockCancel", "type": "Bytes" }, + { "name": "hashLockWithdraw", "type": "Bytes" }, + { "name": "issuedAmount", "type": "BigInt" }, + { "name": "totalAmountOfAssignedBonds", "type": "BigInt!" }, + { "name": "terminated", "type": "Boolean!" }, + { "name": "isReplacementBond", "type": "Boolean!" }, + { "name": "oldBondId", "type": "BigInt" } + ] + }, + { + "name": "Holder", + "fields": [ + { "name": "contract", "type": "BondFacet!" }, + { "name": "account", "type": "Account!" }, + { "name": "amount", "type": "BigInt!" }, + { "name": "bondId", "type": "String!" }, + { "name": "dateOfOwnership", "type": "BigInt!"} + ] + }, + { + "name": "Transfer", + "fields": [ + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondTransferId", "type": "String!"}, + { "name": "from", "type": "Account!" }, + { "name": "to", "type": "Account!" }, + { "name": "bondId", "type": "String! " }, + { "name": "transferDate", "type": "BigInt!" }, + { "name": "amount", "type": "BigInt!" } + ] + }, + { + "name": "CouponList", + "fields": [ + { "name": "contract", "type": "BondFacet!" }, + { "name": "couponDate", "type": "[BigInt!]!" }, + { "name": "newCouponDate", "type": "[BigInt!]!" }, + { "name": "remainingCapital", "type": "[BigDecimal!]!" }, + { "name": "capitalRepayment", "type": "[BigDecimal!]!" }, + { "name": "grossInterestRate", "type": "[BigDecimal!]!" }, + { "name": "netInterestRate", "type": "[BigDecimal!]!" }, + { "name": "grossInterest", "type": "[BigDecimal!]!" }, + { "name": "netInterest", "type": "[BigDecimal!]!" }, + { "name": "feeAmount", "type": "[BigDecimal!]!" }, + { "name": "stepUp", "type": "[BigDecimal!]!" }, + { "name": "stepDown", "type": "[BigDecimal!]!" }, + { "name": "fee", "type": "[BigDecimal!]!" }, + { "name": "status", "type": "[String!]!"}, + { "name": "capitalAndInterest", "type": "[BigDecimal!]!" }, + { "name": "totalToBeRepaid", "type": "BigDecimal!" }, + { "name": "totalAmountRepaid", "type": "BigDecimal!" }, + { "name": "capitalRepaid", "type": "BigDecimal!" }, + { "name": "interestRepaid", "type": "BigDecimal!" }, + { "name": "interestTotal", "type": "BigDecimal!" } + ] + }, + { + "name": "BondInitialized", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "coupure", "type": "BigInt!" }, + { "name": "interestNum", "type": "BigDecimal!" }, + { "name": "interestDen", "type": "BigDecimal!" }, + { "name": "withholdingTaxNum", "type": "BigDecimal!" }, + { "name": "withholdingTaxDen", "type": "BigDecimal!" }, + { "name": "periodicInterestRate", "type": "BigDecimal!" }, + { "name": "periodicity", "type": "BigInt!" }, + { "name": "duration", "type": "BigInt!" }, + { "name": "methodOfRepayment", "type": "BigInt!" }, + { "name": "netReturn", "type": "BigDecimal!" }, + { "name": "maxSupply", "type": "BigDecimal!" }, + { "name": "formOfFinancing", "type": "BigInt!" } + ] + }, + { + "name": "BondParametersEdited", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "coupure", "type": "BigInt!" }, + { "name": "interestNum", "type": "BigDecimal!" }, + { "name": "interestDen", "type": "BigDecimal!" }, + { "name": "withholdingTaxNum", "type": "BigDecimal!" }, + { "name": "withholdingTaxDen", "type": "BigDecimal!" }, + { "name": "periodicInterestRate", "type": "BigDecimal!" }, + { "name": "periodicity", "type": "BigInt!" }, + { "name": "duration", "type": "BigInt!" }, + { "name": "methodOfRepayment", "type": "BigInt!" }, + { "name": "netReturn", "type": "BigDecimal!" }, + { "name": "maxSupply", "type": "BigDecimal!" }, + { "name": "formOfFinancing", "type": "BigInt!" } + ] + }, + { + "name": "CouponsComputed", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "couponDates", "type": "[BigInt!]" }, + { "name": "remainingCapital", "type": "[BigDecimal!]" }, + { "name": "capitalRepayment", "type": "[BigDecimal!]" }, + { "name": "grossCoupons", "type": "[BigDecimal!]" }, + { "name": "netCoupons", "type": "[BigDecimal!]" } + ] + }, + { + "name": "BondsWithdrawn", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "holder", "type": "Account!" }, + { "name": "amount", "type": "BigInt!" } + ] + }, + { + "name": "BondIssued", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "issueDate", "type": "BigInt!" }, + { "name": "issuedAmount", "type": "BigInt!" } + ] + }, + { + "name": "BondsBurned", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "holder", "type": "Account!" }, + { "name": "amount", "type": "BigInt!" } + ] + }, + { + "name": "BalloonRateSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "balloonRateNum", "type": "BigDecimal!" }, + { "name": "balloonRateDen", "type": "BigDecimal!" } + ] + }, + { + "name": "CostEmittentSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "costEmittentNum", "type": "BigDecimal!" }, + { "name": "costEmittentDen", "type": "BigDecimal!" } + ] + }, + { + "name": "GracePeriodSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "gracePeriodDuration", "type": "BigInt!" } + ] + }, + { + "name": "CapitalAmortizationFreePeriodSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "CapitalAmortizationPeriodDuration", "type": "BigInt!" } + ] + }, + { + "name": "InvestorsCountChanged", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "investorsCount", "type": "BigInt!" } + ] + }, + { + "name": "RevocationsCountChanged", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "revocationsCount", "type": "BigInt!" } + ] + }, + { + "name": "ReservedAmountChanged", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "reservedAmount", "type": "BigInt!" } + ] + }, + { + "name": "CouponStatusChanged", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "lineNumber", "type": "BigInt!" } + ] + }, + { + "name": "CampaignStartAndEndDateSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "campaignStartDate", "type": "BigInt!" }, + { "name": "campaignEndDate", "type": "BigInt!" } + ] + }, + { + "name": "CampaignPaused", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" } + ] + }, + { + "name": "CampaignUnpaused", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" } + ] + }, + { + "name": "MinAndMaxAmountSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "minAmount", "type": "BigInt!" }, + { "name": "maxAmount", "type": "BigInt!" }, + { "name": "maxAmountPerInvestor", "type": "BigDecimal!" } + ] + }, + { + "name": "IssueDateSet", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "issueDate", "type": "BigInt!" } + ] + }, + { + "name": "BondTransferred", + "parent": "Event", + "fields": [ + { "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondTransferId", "type": "String!"}, + { "name": "bondId", "type": "BigInt!" }, + { "name": "oldAccount", "type": "Account!" }, + { "name": "newAccount", "type": "Account!" }, + { "name": "amount", "type": "BigInt!" } + ] + } +] diff --git a/subgraph/datasources/diamond.ts b/subgraph/datasources/diamond.ts new file mode 100755 index 0000000..d00215b --- /dev/null +++ b/subgraph/datasources/diamond.ts @@ -0,0 +1,720 @@ +import { events, transactions } from '@amxx/graphprotocol-utils'; +import { BigDecimal, BigInt, Bytes } from '@graphprotocol/graph-ts'; +import { + BalloonRateSet as BalloonRateSetEvent, + BondInitialized as BondInitializedEvent, + BondIssued as BondIssuedEvent, + BondParametersEdited as BondParametersEditedEvent, + BondsWithdrawn as BondsWithdrawnEvent, + CampaignPaused as CampaignPausedEvent, + CampaignStartAndEndDateSet as CampaignStartAndEndDateSetEvent, + CampaignUnpaused as CampaignUnpausedEvent, + CapitalAmortizationFreePeriodSet as CapitalAmortizationFreePeriodSetEvent, + CouponsComputed as CouponsComputedEvent, + GracePeriodSet as GracePeriodSetEvent, + InvestorsCountChanged as InvestorsCountChangedEvent, + IssueDateSet as IssueDateSetEvent, + MinAndMaxAmountSet as MinAndMaxAmountSetEvent, + RevocationsCountChanged as RevocationsCountChangedEvent, + BondTransferred as BondTransferredEvent, + ReservedAmountChanged as ReservedAmountChangedEvent, + CouponStatusChanged as CouponStatusChangedEvent, +} from '../generated/diamond/BondFacet'; +import { + BalloonRateSet, + BondInitialized, + BondIssued, + BondParametersEdited, + BondsWithdrawn, + CampaignPaused, + CampaignStartAndEndDateSet, + CampaignUnpaused, + CapitalAmortizationFreePeriodSet, + CouponsComputed, + GracePeriodSet, + InvestorsCountChanged, + IssueDateSet, + MinAndMaxAmountSet, + RevocationsCountChanged, + BondTransferred, + ReservedAmountChanged, + CouponStatusChanged, +} from '../generated/schema'; +import { + fetchBond, + fetchBondFacet, + fetchCouponList, + fetchHolder, + fetchTransfer, +} from '../fetch/diamond'; + +const scale = BigDecimal.fromString('1000000000000000000'); + +export function handleBondInitialized(event: BondInitializedEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BondInitialized(events.id(event).concat('-bondInitialized')); + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.coupure = event.params.coupure; + ev.interestNum = BigDecimal.fromString(event.params.interestNum.toString()); + ev.interestDen = BigDecimal.fromString(event.params.interestDen.toString()); + ev.periodicInterestRate = BigDecimal.fromString( + event.params.periodicInterestRate.toString() + ); + ev.withholdingTaxNum = BigDecimal.fromString( + event.params.withholdingTaxNum.toString() + ); + ev.withholdingTaxDen = BigDecimal.fromString( + event.params.withholdingTaxDen.toString() + ); + ev.periodicity = event.params.periodicity; + ev.methodOfRepayment = event.params.methodOfRepayment; + ev.formOfFinancing = event.params.formOfFinancing; + ev.duration = event.params.duration; + ev.netReturn = BigDecimal.fromString(event.params.netReturn.toString()); + ev.maxSupply = BigDecimal.fromString(event.params.maxSupply.toString()).div( + scale + ); + + const bond = fetchBond(contract, event.params.bondId.toString()); + bond.coupure = event.params.coupure; + bond.withholdingTaxRate = ev.withholdingTaxNum.div(ev.withholdingTaxDen); + bond.grossInterestRate = ev.interestNum.div(ev.interestDen); + bond.netReturn = ev.netReturn.div(scale); + bond.periodicInterestRate = ev.periodicInterestRate.div(scale); + bond.duration = ev.duration; + bond.maxSupply = ev.maxSupply; + bond.status = 'Active'; + + if (ev.methodOfRepayment == BigInt.fromString('0')) { + bond.methodOfRepayment = 'Bullet'; + } else if (ev.methodOfRepayment == BigInt.fromString('1')) { + bond.methodOfRepayment = 'Degressive'; + } else if (ev.methodOfRepayment == BigInt.fromString('2')) { + bond.methodOfRepayment = 'Balloon'; + } else if (ev.methodOfRepayment == BigInt.fromString('3')) { + bond.methodOfRepayment = 'WithCapitalAmortizationFreePeriod'; + } else if (ev.methodOfRepayment == BigInt.fromString('4')) { + bond.methodOfRepayment = 'WithGracePeriod'; + } + + if (ev.formOfFinancing == BigInt.fromString('0')) { + bond.formOfFinancing = 'Bond'; + } else if (ev.formOfFinancing == BigInt.fromString('1')) { + bond.formOfFinancing = 'SubordinatedBond'; + } + + if (ev.periodicity == BigInt.fromString('0')) { + bond.periodicity = 'Annual'; + } else if (ev.periodicity == BigInt.fromString('1')) { + bond.periodicity = 'Quarterly'; + } else if (ev.periodicity == BigInt.fromString('2')) { + bond.periodicity = 'Monthly'; + } + ////ev.save() ; + bond.save(); +} + +export function handleBondParametersEdited( + event: BondParametersEditedEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BondParametersEdited( + events.id(event).concat('-bondParametersEdited') + ); + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.coupure = event.params.coupure; + ev.interestNum = BigDecimal.fromString(event.params.interestNum.toString()); + ev.interestDen = BigDecimal.fromString(event.params.interestDen.toString()); + ev.periodicInterestRate = BigDecimal.fromString( + event.params.periodicInterestRate.toString() + ); + ev.withholdingTaxNum = BigDecimal.fromString( + event.params.withholdingTaxNum.toString() + ); + ev.withholdingTaxDen = BigDecimal.fromString( + event.params.withholdingTaxDen.toString() + ); + ev.periodicity = event.params.periodicity; + ev.methodOfRepayment = event.params.methodOfRepayment; + ev.formOfFinancing = event.params.formOfFinancing; + ev.duration = event.params.duration; + ev.netReturn = BigDecimal.fromString(event.params.netReturn.toString()); + ev.maxSupply = BigDecimal.fromString(event.params.maxSupply.toString()).div( + scale + ); + + const bond = fetchBond(contract, event.params.bondId.toString()); + bond.coupure = event.params.coupure; + bond.withholdingTaxRate = ev.withholdingTaxNum.div(ev.withholdingTaxDen); + bond.grossInterestRate = ev.interestNum.div(ev.interestDen); + bond.netReturn = ev.netReturn.div(scale); + bond.periodicInterestRate = ev.periodicInterestRate.div(scale); + bond.duration = ev.duration; + bond.maxSupply = ev.maxSupply; + + if (ev.methodOfRepayment == BigInt.fromString('0')) { + bond.methodOfRepayment = 'Bullet'; + } else if (ev.methodOfRepayment == BigInt.fromString('1')) { + bond.methodOfRepayment = 'Degressive'; + } else if (ev.methodOfRepayment == BigInt.fromString('2')) { + bond.methodOfRepayment = 'Balloon'; + } else if (ev.methodOfRepayment == BigInt.fromString('3')) { + bond.methodOfRepayment = 'WithCapitalAmortizationFreePeriod'; + } else if (ev.methodOfRepayment == BigInt.fromString('4')) { + bond.methodOfRepayment = 'WithGracePeriod'; + } + + if (ev.formOfFinancing == BigInt.fromString('0')) { + bond.formOfFinancing = 'Bond'; + } else if (ev.formOfFinancing == BigInt.fromString('1')) { + bond.formOfFinancing = 'SubordinatedBond'; + } + + if (ev.periodicity == BigInt.fromString('0')) { + bond.periodicity = 'Annual'; + } else if (ev.periodicity == BigInt.fromString('1')) { + bond.periodicity = 'Quarterly'; + } else if (ev.periodicity == BigInt.fromString('2')) { + bond.periodicity = 'Monthly'; + } + ////ev.save() ; + bond.save(); +} + +export function handleCouponsComputed(event: CouponsComputedEvent): void { + const contract = fetchBondFacet(event.address, '0'); + + const ev = new CouponsComputed(events.id(event).concat('-couponsComputed')); + const couponList = fetchCouponList(contract, event.params.bondId.toString()); + const bond = fetchBond(contract, event.params.bondId.toString()); + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + + const capitalAndInterest: BigDecimal[] = []; + const remainingCapital: BigDecimal[] = []; + const grossInterest: BigDecimal[] = []; + const netInterest: BigDecimal[] = []; + const capitalRepayments: BigDecimal[] = []; + const stepUpDownFee: BigDecimal[] = []; + const grossInterestRate: BigDecimal[] = []; + const netInterestRate: BigDecimal[] = []; + const newDates: BigInt[] = []; + const status: String[] = []; + couponList.interestTotal = BigDecimal.fromString('0'); + couponList.totalToBeRepaid = BigDecimal.fromString('0'); + for (let i = 0; i < event.params.remainingCapital.length; i++) { + status.push('Todo'); + capitalAndInterest.push( + BigDecimal.fromString(event.params.netCouponRates[i].toString()) + .plus( + BigDecimal.fromString(event.params.capitalRepayments[i].toString()) + ) + .div(scale) + ); + + remainingCapital.push( + BigDecimal.fromString(event.params.remainingCapital[i].toString()).div( + scale + ) + ); + if (i == 0) { + couponList.totalToBeRepaid = remainingCapital[i]; + } + grossInterest.push( + BigDecimal.fromString(event.params.grossCouponRates[i].toString()).div( + scale + ) + ); + netInterest.push( + BigDecimal.fromString(event.params.netCouponRates[i].toString()).div( + scale + ) + ); + couponList.interestTotal = couponList.interestTotal.plus(netInterest[i]); + couponList.totalToBeRepaid = couponList.totalToBeRepaid.plus( + netInterest[i] + ); + capitalRepayments.push( + BigDecimal.fromString(event.params.capitalRepayments[i].toString()).div( + scale + ) + ); + stepUpDownFee.push(BigDecimal.fromString('0')); + grossInterestRate.push(bond.periodicInterestRate); + netInterestRate.push( + bond.periodicInterestRate.times( + BigDecimal.fromString('1').minus(bond.withholdingTaxRate) + ) + ); + newDates.push(BigInt.fromString('0')); + } + couponList.capitalAndInterest = capitalAndInterest; + couponList.remainingCapital = remainingCapital; + couponList.capitalRepayment = capitalRepayments; + couponList.grossInterest = grossInterest; + couponList.netInterest = netInterest; + couponList.couponDate = event.params.couponDates; + couponList.newCouponDate = newDates; + couponList.stepDown = stepUpDownFee; + couponList.stepUp = stepUpDownFee; + couponList.fee = stepUpDownFee; + couponList.netInterestRate = netInterestRate; + couponList.grossInterestRate = grossInterestRate; + couponList.feeAmount = stepUpDownFee; + couponList.status = status; + ev.grossCoupons = grossInterest; + ev.netCoupons = netInterest; + ev.capitalRepayment = capitalRepayments; + ev.remainingCapital = remainingCapital; + ev.couponDates = event.params.couponDates; + + couponList.save(); + + ////ev.save() ; +} + +/*{ "name": "emitter", "type": "Account!" }, + { "name": "transaction", "type": "Transaction!" }, + { "name": "timestamp", "type": "BigInt!" }, + { "name": "contract", "type": "BondFacet!" }, + { "name": "bondId", "type": "BigInt!" }, + { "name": "issueDate", "type": "BigInt!" }, + { "name": "issuedAmount", "type": "BigInt!" }*/ + +export function handleBondIssued(event: BondIssuedEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BondIssued(events.id(event).concat('-bondIssued')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.issueDate = event.params.timestamp; + ev.issuedAmount = event.params.issuedAmount; + + bond.status = 'Issued'; + bond.issueDate = ev.issueDate; + bond.issuedAmount = event.params.issuedAmount; + + ////ev.save() ; + bond.save(); +} + +export function handleBondsWithdrawn(event: BondsWithdrawnEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BondsWithdrawn(events.id(event).concat('-bondsWithdrawn')); + const bond = fetchBond(contract, event.params.bondId.toString()); + const hldr = fetchHolder( + contract, + event.params.holder, + event.params.bondId.toString() + ); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.holder = event.params.holder; + ev.amount = event.params.amount; + + const holders: Bytes[] = []; + const amount: BigInt[] = []; + + const reservingAddresses: Bytes[] = []; + const reservedAmount: Bytes[] = []; + + //const holder = fetchAccount(event.params.holder); + const holder = ev.holder; + let holderExist = false; + + hldr.amount = hldr.amount.plus(ev.amount); + if (hldr.dateOfOwnership == BigInt.fromString('0')) { + hldr.dateOfOwnership = event.block.timestamp; + } else { + if (event.block.timestamp >= hldr.dateOfOwnership) { + hldr.dateOfOwnership = event.block.timestamp; + } + } + + /*const hldrBonds = hldr.bondIds; + const hldrAmounts = hldr.amount; + + const hldrBondsUpdated: BigInt[] = []; + const hldrAmountsUpdated: BigInt[]= []; + + let holderHasBonds = false; + for(let i = 0; i < hldrBonds.length; i++){ + if(hldrBonds[i] == ev.bondId){ + holderHasBonds = true; + hldrAmountsUpdated.push(hldrAmounts[i].plus(ev.amount)); + } + else{ + hldrAmountsUpdated.push(hldrAmounts[i]); + } + hldrBondsUpdated.push(hldrBonds[i]); + } + if(holderHasBonds == false){ + hldrBondsUpdated.push(ev.bondId); + hldrAmountsUpdated.push(ev.amount); + } + + hldr.bondIds = hldrBondsUpdated; + hldr.amount = hldrAmountsUpdated;*/ + + for (let i = 0; i < bond.holders.length; i++) { + holders.push(bond.holders[i]); + if (bond.holders[i] == holder) { + holderExist = true; + amount.push(bond.holdersAmount[i].plus(ev.amount)); + } else { + amount.push(bond.holdersAmount[i]); + } + } + if (holderExist == false) { + holders.push(holder); + amount.push(ev.amount); + } + for (let i = 0; i < bond.reservedAmountByAddresses.length; i++) { + if (bond.reservationsByAddresses[i] != holder) { + reservingAddresses.push(bond.reservationsByAddresses[i]); + reservedAmount.push(bond.reservedAmountByAddresses[i]); + } + } + + bond.holders = holders; + bond.holdersAmount = amount; + bond.reservationsByAddresses = reservingAddresses; + bond.reservedAmountByAddresses = reservedAmount; + bond.totalAmountOfAssignedBonds.plus(ev.amount); + + ////ev.save(); + bond.save(); + hldr.save(); +} + +export function handleInvestorsCountChanged( + event: InvestorsCountChangedEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new InvestorsCountChanged( + events.id(event).concat('-investorsCountChanged') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.investorsCount = event.params.investorsCount; + + bond.investorsCount = event.params.investorsCount; + ////ev.save() ; + bond.save(); +} + +export function handleRevocationsCountChanged( + event: RevocationsCountChangedEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new RevocationsCountChanged( + events.id(event).concat('-revocationsCountChanged') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.revocationsCount = event.params.revocationsCount; + + bond.revocationsCount = event.params.revocationsCount; + ////ev.save() ; + bond.save(); +} + +export function handleReservedAmountChanged( + event: ReservedAmountChangedEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new ReservedAmountChanged( + events.id(event).concat('-reservedAmountChanged') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.reservedAmount = event.params.reservedAmount; + + bond.reservedAmount = ev.reservedAmount; + ////ev.save() ; + bond.save(); +} + +export function handleMinAndMaxAmountSet(event: MinAndMaxAmountSetEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new MinAndMaxAmountSet( + events.id(event).concat('-minAndMaxAmountSet') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.minAmount = event.params.minAmount; + ev.maxAmount = event.params.maxAmount; + ev.maxAmountPerInvestor = BigDecimal.fromString( + event.params.maxAmountPerInvestor.toString() + ); + + bond.minAmount = event.params.minAmount; + bond.maxAmount = event.params.maxAmount; + bond.maxAmountPerInvestor = ev.maxAmountPerInvestor; + + ////ev.save() ; + bond.save(); +} + +export function handleBalloonRateSet(event: BalloonRateSetEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BalloonRateSet(events.id(event).concat('-balloonRateSet')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.balloonRateNum = BigDecimal.fromString( + event.params.balloonRateNum.toString() + ); + ev.balloonRateDen = BigDecimal.fromString( + event.params.balloonRateDen.toString() + ); + + if (ev.balloonRateDen != BigDecimal.fromString('0')) { + bond.balloonPercentage = ev.balloonRateNum.div(ev.balloonRateDen); + } else { + bond.balloonPercentage = BigDecimal.fromString('0'); + } + ////ev.save() ; + bond.save(); +} + +export function handleGracePeriodSet(event: GracePeriodSetEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new GracePeriodSet(events.id(event).concat('-gracePeriodSet')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.gracePeriodDuration = event.params.gracePeriodDuration; + + bond.gracePeriod = event.params.gracePeriodDuration; + + ////ev.save() ; + bond.save(); +} + +export function handleCapitalAmortizationFreePeriodSet( + event: CapitalAmortizationFreePeriodSetEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new CapitalAmortizationFreePeriodSet( + events.id(event).concat('-capitalAmortizationFreePeriodSet') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.CapitalAmortizationPeriodDuration = + event.params.capitalAmortizationFreePeriodDuration; + bond.capitalAmortizationFreePeriod = + event.params.capitalAmortizationFreePeriodDuration; + + ////ev.save() ; + bond.save(); +} + +export function handleCampaignStartAndEndDateSet( + event: CampaignStartAndEndDateSetEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new CampaignStartAndEndDateSet( + events.id(event).concat('-campaignStartAndEndDateSet') + ); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.campaignStartDate = event.params.startDate; + ev.campaignEndDate = event.params.endDate; + + bond.campaignEndDate = ev.campaignEndDate; + bond.campaignStartDate = ev.campaignStartDate; + + ////ev.save() ; + bond.save(); +} + +export function handleCampaignPaused(event: CampaignPausedEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new CampaignPaused(events.id(event).concat('-campaignPaused')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + bond.paused = true; + bond.status = 'Paused'; + + ////ev.save() ; + bond.save(); +} + +export function handleCampaignUnpaused(event: CampaignUnpausedEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new CampaignUnpaused(events.id(event).concat('-campaignUnpaused')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + bond.paused = false; + bond.status = 'Active'; + + ////ev.save() ; + bond.save(); +} + +export function handleIssueDateSet(event: IssueDateSetEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new IssueDateSet(events.id(event).concat('-issueDateSet')); + const bond = fetchBond(contract, event.params.bondId.toString()); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.issueDate = event.params.issueDate; + + bond.issueDate = event.params.issueDate; + + ////ev.save() ; + bond.save(); +} + +export function handleBondTransferred(event: BondTransferredEvent): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new BondTransferred(events.id(event).concat('-bondTransferred')); + const bond = fetchBond(contract, event.params.bondId.toString()); + const transfer = fetchTransfer(contract, event.params.bondTransferId); + const oldHolder = fetchHolder( + contract, + event.params.oldAccount, + event.params.bondId.toString() + ); + const newHolder = fetchHolder( + contract, + event.params.newAccount, + event.params.bondId.toString() + ); + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.oldAccount = event.params.oldAccount; + ev.newAccount = event.params.newAccount; + ev.amount = event.params.amount; + + oldHolder.amount = oldHolder.amount.minus(ev.amount); + newHolder.amount = newHolder.amount.plus(ev.amount); + + if (newHolder.dateOfOwnership == BigInt.fromString('0')) { + newHolder.dateOfOwnership = event.block.timestamp; + } else { + if (event.block.timestamp >= newHolder.dateOfOwnership) { + newHolder.dateOfOwnership = event.block.timestamp; + } + } + + oldHolder.save(); + newHolder.save(); + + transfer.amount = ev.amount; + transfer.contract = contract.id; + transfer.from = event.params.oldAccount; + transfer.to = event.params.newAccount; + transfer.bondId = event.params.bondId.toString(); + transfer.transferDate = ev.timestamp; + + transfer.save(); + + ////ev.save() ; + bond.save(); +} + +export function handleCouponStatusChanged( + event: CouponStatusChangedEvent +): void { + const contract = fetchBondFacet(event.address, '0'); + const ev = new CouponStatusChanged( + events.id(event).concat('-CouponStatusChanged') + ); + const couponList = fetchCouponList(contract, event.params.bondId.toString()); + const status: String[] = []; + + for (let i = 0; i < couponList.status.length; i++) { + if (i == event.params.lineNumber.toU32()) { + status.push('Executed'); + } else { + status.push(couponList.status[i]); + } + } + + ev.emitter = Bytes.fromHexString(contract.id); + ev.transaction = transactions.log(event).id; + ev.timestamp = event.block.timestamp; + ev.contract = contract.id; + ev.bondId = event.params.bondId; + ev.lineNumber = event.params.lineNumber; + + couponList.save(); + ////ev.save() ; +} diff --git a/subgraph/datasources/diamond.yaml b/subgraph/datasources/diamond.yaml new file mode 100755 index 0000000..dbec692 --- /dev/null +++ b/subgraph/datasources/diamond.yaml @@ -0,0 +1,52 @@ + - kind: ethereum/contract + name: {id} + network: {chain} + source: + address: "{address}" + abi: BondFacet + startBlock: {startBlock} + mapping: + kind: ethereum/events + apiVersion: 0.0.5 + language: wasm/assemblyscript + entities: + - BondFacet + abis: + - name: BondFacet + file: {root}/../out/BondFacet.sol/BondFacet.json + eventHandlers: + - event: BondInitialized(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) + handler: handleBondInitialized + - event: BondParametersEdited(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) + handler: handleBondParametersEdited + - event: CouponsComputed(uint256,uint256[],uint256[],uint256[],uint256[],uint256[]) + handler: handleCouponsComputed + - event: BondIssued(uint256,uint256,uint256) + handler: handleBondIssued + - event: BondsWithdrawn(string,uint256,address,uint256) + handler: handleBondsWithdrawn + - event: BalloonRateSet(uint256,uint256,uint256) + handler: handleBalloonRateSet + - event: GracePeriodSet(uint256,uint256) + handler: handleGracePeriodSet + - event: CapitalAmortizationFreePeriodSet(uint256,uint256) + handler: handleCapitalAmortizationFreePeriodSet + - event: InvestorsCountChanged(uint256,uint256) + handler: handleInvestorsCountChanged + - event: CampaignStartAndEndDateSet(uint256,uint256,uint256) + handler: handleCampaignStartAndEndDateSet + - event: CampaignPaused(uint256) + handler: handleCampaignPaused + - event: CampaignUnpaused(uint256) + handler: handleCampaignUnpaused + - event: MinAndMaxAmountSet(uint256,uint256,uint256,uint256) + handler: handleMinAndMaxAmountSet + - event: IssueDateSet(uint256,uint256) + handler: handleIssueDateSet + - event: BondTransferred(string,uint256,address,address,uint256) + handler: handleBondTransferred + - event: ReservedAmountChanged(uint256,uint256) + handler: handleReservedAmountChanged + - event: CouponStatusChanged(uint256,uint256) + handler: handleCouponStatusChanged + file: {file} diff --git a/subgraph/fetch/.gitkeep b/subgraph/fetch/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/subgraph/fetch/account.ts b/subgraph/fetch/account.ts new file mode 100755 index 0000000..aa838b6 --- /dev/null +++ b/subgraph/fetch/account.ts @@ -0,0 +1,8 @@ +import { Address, Bytes } from '@graphprotocol/graph-ts'; +import { Account } from '../generated/schema'; + +export function fetchAccount(address: Address): Account { + const account = new Account(Bytes.fromHexString(address.toHex())); + account.save(); + return account; +} diff --git a/subgraph/fetch/diamond.ts b/subgraph/fetch/diamond.ts new file mode 100755 index 0000000..fc5fd0b --- /dev/null +++ b/subgraph/fetch/diamond.ts @@ -0,0 +1,156 @@ +import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts'; +import { + Bond, + BondFacet, + CouponList, + Holder, + Transfer, +} from '../generated/schema'; +import { fetchAccount } from './account'; + +export function fetchBondFacet( + address: Address, + facetOffset: string +): BondFacet { + const account = fetchAccount(address); + const contractId = account.id.toHex().concat('/').concat(facetOffset); + //let contract = BondFacet.load(account.id.toHex()); + let contract = BondFacet.load(contractId); + + if (contract == null) { + //contract = new BondFacet(account.id.toHex()); + contract = new BondFacet(contractId); + account.asBondFacet = contractId; + //account.asBondFacet = contract.id; + //contract.asAccount = account.id.toHex(); + + contract.save(); + account.save(); + } + + return contract as BondFacet; +} + +export function fetchCouponList( + contract: BondFacet, + bondId: string +): CouponList { + let couponList = CouponList.load(bondId); + if (couponList == null) { + couponList = new CouponList(bondId); + couponList.contract = contract.id; + couponList.remainingCapital = []; + couponList.capitalRepayment = []; + couponList.grossInterestRate = []; + couponList.netInterestRate = []; + couponList.grossInterest = []; + couponList.netInterest = []; + couponList.stepUp = []; + couponList.stepDown = []; + couponList.fee = []; + couponList.capitalAndInterest = []; + couponList.couponDate = []; + couponList.newCouponDate = []; + couponList.feeAmount = []; + couponList.status = []; + couponList.totalToBeRepaid = BigDecimal.fromString('0'); + couponList.totalAmountRepaid = BigDecimal.fromString('0'); + couponList.capitalRepaid = BigDecimal.fromString('0'); + couponList.interestRepaid = BigDecimal.fromString('0'); + couponList.interestTotal = BigDecimal.fromString('0'); + couponList.save(); + } + return couponList as CouponList; +} + +export function fetchHolder( + contract: BondFacet, + account: Address, + bondId: string +): Holder { + const holderId = account.toString().concat('/').concat(bondId); + let holder = Holder.load(holderId); + + if (holder == null) { + holder = new Holder(holderId); + holder.contract = contract.id; + holder.account = account; + holder.amount = BigInt.fromString('0'); + holder.bondId = bondId; + holder.dateOfOwnership = BigInt.fromString('0'); + holder.save(); + } + return holder; +} + +export function fetchTransfer( + contract: BondFacet, + transferId: string +): Transfer { + //const transferId = bondId.concat('/').concat(from).concat('/').concat(to).concat('/').concat(timestamp); + let transfer = Transfer.load(transferId); + if (transfer == null) { + transfer = new Transfer(transferId); + transfer.bondTransferId = transferId; + transfer.contract = contract.id; + transfer.bondId = ''; + transfer.transferDate = BigInt.fromString('0'); + transfer.amount = BigInt.fromString('0'); + transfer.save(); + } + return transfer; +} + +export function fetchBond(contract: BondFacet, bondId: string): Bond { + let bond = Bond.load(bondId); + if (bond == null) { + bond = new Bond(bondId); + bond.contract = contract.id; + bond.coupure = BigInt.fromString('0'); + bond.grossInterestRate = BigDecimal.fromString('0'); + bond.netReturn = BigDecimal.fromString('0'); + bond.withholdingTaxRate = BigDecimal.fromString('0'); + bond.periodicInterestRate = BigDecimal.fromString('0'); + bond.holders = []; + bond.holdersAmount = []; + bond.reservationsByAddresses = []; + bond.reservedAmountByAddresses = []; + bond.reservedAmount = BigInt.fromString('0'); + bond.periodicity = ''; + bond.methodOfRepayment = ''; + bond.duration = BigInt.fromString('0'); + bond.gracePeriod = BigInt.fromString('0'); + bond.balloonPercentage = BigDecimal.fromString('0'); + bond.capitalAmortizationFreePeriod = BigInt.fromString('0'); + bond.costEmittent = BigDecimal.fromString('0'); + bond.investorsCount = BigInt.fromString('0'); + bond.revocationsCount = BigInt.fromString('0'); + bond.campaignStartDate = BigInt.fromString('0'); + bond.campaignEndDate = BigInt.fromString('0'); + bond.paused = false; + bond.maxSupply = BigDecimal.fromString('0'); + bond.maxAmountPerInvestor = BigDecimal.fromString('0'); + bond.campaignStartDate = BigInt.fromString('0'); + bond.campaignEndDate = BigInt.fromString('0'); + bond.maxAmount = BigInt.fromString('0'); + bond.minAmount = BigInt.fromString('0'); + bond.issueDate = BigInt.fromString('0'); + bond.formOfFinancing = ''; + bond.status = ''; + bond.withdrawRef = ''; + bond.cancelRef = ''; + bond.terminated = false; + bond.isReplacementBond = false; + //bond.hashLockCancel =Bytes.fromHexString(''); + //bond.hashLockWithdraw = Bytes.fromHexString(''); + + bond.withdrawStartTime = BigInt.fromString('0'); + bond.withdrawEndTime = BigInt.fromString('0'); + bond.cancelStartTime = BigInt.fromString('0'); + bond.cancelEndTime = BigInt.fromString('0'); + bond.issuedAmount = BigInt.fromString('0'); + bond.totalAmountOfAssignedBonds = BigInt.fromString('0'); + bond.save(); + } + return bond as Bond; +} diff --git a/subgraph/package.json b/subgraph/package.json new file mode 100644 index 0000000..b360e22 --- /dev/null +++ b/subgraph/package.json @@ -0,0 +1,19 @@ +{ + "name": "solidity-token-diamond-bond", + "version": "7.0.0", + "description": "Solidity Token Diamond Bond", + "scripts": {}, + "keywords": [ + "solidity", + "subgraph" + ], + "author": "SettleMint ", + "license": "MIT", + "dependencies": { + "@amxx/graphprotocol-utils": "1.1.0", + "@graphprotocol/graph-cli": "0.71.0", + "@graphprotocol/graph-ts": "0.35.1", + "@openzeppelin/contracts": "5.0.2", + "@openzeppelin/subgraphs": "0.1.8-5" + } +} diff --git a/subgraph/subgraph.config.template.json b/subgraph/subgraph.config.template.json new file mode 100644 index 0000000..2572a51 --- /dev/null +++ b/subgraph/subgraph.config.template.json @@ -0,0 +1,18 @@ +{ + "output": "generated/solidity-diamond-bond.", + "chain": "localhost", + "datasources": [ + { + "name": "BondFacet", + "address": "0x0000000000000000000000000000000000000000", + "startBlock": 0, + "module": ["diamond"] + }, + { + "name": "GenericToken", + "address": "0x0000000000000000000000000000000000000000", + "startBlock": 0, + "module": ["erc20", "pausable", "accesscontrol"] + } + ] +} diff --git a/subgraph/tsconfig.json b/subgraph/tsconfig.json new file mode 100755 index 0000000..69150c5 --- /dev/null +++ b/subgraph/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", + "include": [ + "datasources", + "fetch" + ] +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 924f97b..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/DiamondBondTest.t.sol b/test/DiamondBondTest.t.sol new file mode 100644 index 0000000..6b9f884 --- /dev/null +++ b/test/DiamondBondTest.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/facets/BondFacet.sol"; +import "../src/facets/ERC1155Facet.sol"; +import "../src/facets/DiamondCutFacet.sol"; +import "../src/facets/DiamondLoupeFacet.sol"; +import "../src/Diamond.sol"; +import "../src/upgradeInitializers/DiamondInit.sol"; +import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol"; +import {BondInitParams} from "../src/libraries/StructBondInit.sol"; +import "../src/interfaces/IDiamond.sol"; + +contract DiamondBondTest is Test { + address ownership; + address owner; + address diamondCutAddress; + address erc1155FacetAddress; + address diamondLoupeFacetAddress; + address bondFacetAddress; + address diamondAddress; + address diamondInitAddress; + + IDiamondLoupe ILoupe; + + function setUp() public { + owner = vm.addr(123); + vm.startPrank(owner); + DiamondCutFacet diamondCut = new DiamondCutFacet(); + diamondCutAddress = address(diamondCut); + + DiamondInit diamondInit = new DiamondInit(); + diamondInitAddress = address(diamondInit); + + ERC1155Facet erc1155Facet = new ERC1155Facet(); + erc1155FacetAddress = address(erc1155Facet); + + DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet(); + diamondLoupeFacetAddress = address(diamondLoupeFacet); + + BondFacet bondFacet = new BondFacet(); + bondFacetAddress = address(bondFacet); + + //Diamond diamond = new Diamond(owner, diamondCutAddress); + + IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](3); + + cuts[0] = IDiamond.FacetCut({ + facetAddress: erc1155FacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: erc1155Facet.getSelectors() + }); + + cuts[1] = IDiamond.FacetCut({ + facetAddress: diamondLoupeFacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: diamondLoupeFacet.getSelectors() + }); + + cuts[2] = IDiamond.FacetCut({ + facetAddress: bondFacetAddress, + action: IDiamond.FacetCutAction.Add, + functionSelectors: bondFacet.getSelectors() + }); + + DiamondArgs memory da = DiamondArgs({ + owner: owner, + init: diamondInitAddress, + initCalldata: abi.encodeWithSelector(bytes4(keccak256("init()"))) + }); + + Diamond diamond = new Diamond(cuts, da); + diamondAddress = address(diamond); + + // Assign diamond address to a state variable for further testing + vm.stopPrank(); + } + + function testInitializeBond() public { + // Create mock parameters for initializing a bond + BondInitParams.BondInit memory params = BondInitParams.BondInit({ + __bondId: 1, + __coupure: 1000, + __interestNum: 5, + __interestDen: 100, + __withholdingTaxNum: 10, + __withholdingTaxDen: 100, + __periodicity: uint256(BondStorage.Periodicity.Annual), + __duration: 24, + __methodOfRepayment: uint256(BondStorage.MethodOfRepayment.Bullet), + __campaignMaxAmount: 100000, + __campaignMinAmount: 1000, + __maxAmountPerInvestor: 5000, + __campaignStartDate: 1713603094, + __expectedIssueDate: 1716195094, + __balloonRateNum: 0, + __balloonRateDen: 0, + __capitalAmortizationDuration: 0, + __gracePeriodDuration: 0, + __formOfFinancing: uint256(BondStorage.FormOfFinancing.Bond) + }); + + // Call initializeBond using the deployed Diamond as the caller + vm.prank(owner); + BondFacet(diamondAddress).initializeBond(params); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..add2669 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist" + }, + "include": [ + "tasks/**/*" + ] +} \ No newline at end of file