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