diff --git a/README.md b/README.md index 24351a5..24154f1 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,57 @@ -# BCA Service Token +# BCA Market for Tokenized Services -### Version: 2024-10-02 -### Copyright: 2024 Alexander Diemand -### [License](./LICENSE): GPLv3 - GNU GENERAL PUBLIC LICENSE Version 3 +#### Version: 2024-11-23 +#### Copyright: 2024 Alexander Diemand +#### [License](./LICENSE): GPLv3 - GNU GENERAL PUBLIC LICENSE Version 3 ## Documentation [see documentation](./doc/README.md) - ## Overview -This project is an evaluation of how to use ERC20 tokens on a blockchain to charge users for their usage of our services in micro-payments without incurring massive transaction fees. -Such a setup shows large flexibility in creating new services and shows great potential for revenue streams from micro-payments. -Users benefit from keeping control over their budget of service usage costs and only expose a minimal stake at any time. -Charging for services is done transparently on-chain. -The user with a positive token balance has access to an agent that helps funding service usage: by time, using fixed budget, ... +We are developing the tokenization of services and have them managed on a blockchain with smart contracts. + +The contracts' behaviour is defined by a fixed set of parameters. -### Why call the internal token GEEZ? -Our services can be compared to mice which need some cheese from time to time to make them happy. -We might call that GEEZ (GEZ), similar to Ethereum's GAS. +Our product creates a market for tokenized services and brings users and service providers together. +![Overview](./doc/img/service_contract.png) ## Principles & Ideas -- there is an on-chain service token (probably named "BCA") and an internal token (named "GEEZ") for micropayments -- users aquire the service token using a fiat on-ramp service -- we mint new tokens to the user on payment in fiat. Later: they might also pay in crypto to some smart contract -- users own the service tokens and can see them in their wallets -- before using a service, users transfer a small amount of service tokens to the service's address -- A GEEZ record of the user's tokens is kept in our database linked to the sender. Proof is the on-chain transaction -- service usage by the user will be deducte from the user's GEEZ account - - (this part could be solved in our own L2 network, one day) -- users can see their GEEZ balance and transactions in a dashboard -- users can optionally add monitoring to their GEEZ budget: as soon as it drops below a certain level, then an email is sent to them asking for refunding +- micropayments in a stable-coin (EUR, USD) +- alternative: ERC20 token pegged to a currency, on/off-ramp from/to fiat +- users keep their funds in their wallets, and only commit a small amount in deposits to the service contract +- optionally, users can subscribe to a bot that deposits every 24 hours the required funds to keep services running +- at any time either the user or the provider can call stop() and the contract halts +- at any time both the user and the provider can withdraw funds from the service contract: up to the calculated balance at the current block time +- the contracts store the necessary information and thus no party relies on a centralized backend for operations + +## Micropayments and balance calculation + +The service contract is created with a set of parameters which are fixed for the lifetime of the contract. + +The calculation of the user's and provider's balances depend on the contracts start time and the current time, taken from the latest block. + +Once the user makes her first deposit, the contract starts. Multiple deposits can be made by the user. And, both parties can withdraw up to their calculated balance. +![User and provider balance in the contract](./doc/img/service_micropayment.png) -## Solidity contract and testing +## Solidity contracts and testing see [README](./bca-token-solidity/README.md) in directory [./bca-token-solidity](./bca-token-solidity/) +## Frontend to interact with service contracts + +Both users and providers connect to this user interface to interact with the service contracts. + +see [README](./bca-token-market/README.md) in directory [./bca-token-market](./bca-token-market/) + ## Frontend for minting/burning of tokens -see [README](./bca-token-app/README.md) in directory [./bca-token-app](./bca-token-app/) +Developping and testing is done using our own ERC20 token that mimics a stable-coin. +see [README](./bca-token-app/README.md) in directory [./bca-token-app](./bca-token-app/) diff --git a/bca-token-solidity/README.md b/bca-token-solidity/README.md index 0caac7f..304b3d6 100644 --- a/bca-token-solidity/README.md +++ b/bca-token-solidity/README.md @@ -20,7 +20,7 @@ in one terminal start a node: ```sh npx hardhat node ``` -in the other deploy the contract +in the other deploy the contracts ```sh npx hardhat ignition deploy ignition/modules/BCA_Token.ts --network localhost npx hardhat ignition deploy ignition/modules/BCA_ServiceManager.ts --network localhost diff --git a/doc/Contracts.md b/doc/Contracts.md new file mode 100644 index 0000000..9d8a48c --- /dev/null +++ b/doc/Contracts.md @@ -0,0 +1,185 @@ +# Smart contracts + +## Contract: BCAServiceToken + +This contract implements an ERC20 token following the definitions from OpenZeppelin: + +Source file: [BCA_ERC20_nf.sol](../bca-token-solidity/contracts/BCA_ERC20_nf.sol) + +Implements interfaces: +- `import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"` + - the `constructor(string memory setName, string memory setSymbol, address setMinter, address setBurner)` + +- `import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"` + - implements access control + - we restrict minting to the indicated minter address as given to the constructor of updated using `setMinterAddress()` + - we restrict burning to the indicated burner address as given to the constructor of updated using `setBurnerAddress()` + +The contract has public fields: +- `address public serviceAddress` +- `address public minterAddress` +- `address public burnerAddress` + +## Contract: BCAServiceManager + +The service manager contract is at the top of the hierarchy of contracts and serves as an entry point. It is parameterized by the ERC20 or stable-coin address that is used throughout the dependent contracts. It stores the addresses of provider controller contracts. + +Source file: [BCA_ServiceManager.sol](../bca-token-solidity/contracts/BCA_ServiceManager.sol) + +Implements interfaces: +- [IServiceManager](../bca-token-solidity/contracts/Iface_ServiceManager.sol) +- [ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) + +The contract has public fields: +- `IERC20 public immutable tokToken` + * address of the ERC20 token as stable-coin +- `address[] public providerControllers` + * array of addresses of linked service controllers (see [BCA_ServiceController.sol](#bca_servicecontrollersol)) +- `mapping (address => ControllerStruct) public deployedControllers` + * mapping of a provider's address to the setup controller + * ControllerStruct is: + ```sol + struct ControllerStruct { + address addrContract; + bool isDeployed; + } + ``` + +The contract has public methods: +- `constructor(address tokAddress)` + * only parameter is the common ERC20/stable-coin +- `function getControllerAddress(address providerAddress) public view returns(address addrController)` + * returns the controller address for a provider's address +- `function isDeployed(address providerAddress) public view returns(bool isdeployed)` + * returns true when the controller is deployed +- `function newController(address providerAddress) external nonReentrant` + * deploys a provider's controller +- `function countServiceControllers() public view returns (uint)` + * returns the number of known controllers by this service manager + +## Contract: BCAServiceController + +The service controller contract is deployed for every provider. +It keeps a list of addresses for the services that the provider offers. + +Source file: [BCA_ServiceController.sol](../bca-token-solidity/contracts/BCA_ServiceController.sol) + +Implements interfaces: +- [IServiceController](../bca-token-solidity/contracts/Iface_ServiceController.sol) +- [ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) + +The contract has public fields: +- `IERC20 public immutable tokToken` + * the common ERC20 or stable-coin +- `address public immutable providerAddress` + * the provider's address +- `address[] public deployedServices` + * an array of linked services + +The contract has public methods: +- `constructor(address setProviderAddress, address tokAddress)` +- `function newService(uint16 maxInstances, uint256 dayPrice) external nonReentrant returns (address)` + * deploys a new service contract with the given parameterization +- `function countServiceContracts() public view returns (uint)` + * returns the number of services in the array, necessary to enumerate the array entries + +## Contract: BCAService + +A service references a provider and the common ERC20/stable-coin and is parameterized with the maximal number of instances and the price of the service per day. + +Source file: [BCA_Service.sol](../bca-token-solidity/contracts/BCA_Service.sol) + +Implements interfaces: +- [IService](../bca-token-solidity/contracts/Iface_Service.sol) +- [ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) + +The contract has public fields: +- `IERC20 public immutable tokToken` +- `uint256 public immutable dayPrice` +- `address public immutable providerAddress` +- `uint16 public immutable maxInstances` +- `address[] public deployedInstances` + +The contract has public methods: +- `constructor(address setProviderAddress, address tokAddress, + uint16 setMaxInstances, uint256 setDayPrice)` +- `function newInstance(address userAddress) external nonReentrant returns (address)` +- `function countServiceInstances() public view returns (uint)` + +## Contract: BCAServiceInstance + +This contract is at the heart of the market and represents a service instance. A user subscribes to a service with a first deposit of funds. This sets the start time of the contract from which the balances are calculated. + +Source file: [BCA_ServiceInstance.sol](../bca-token-solidity/contracts/BCA_ServiceInstance.sol) + +Implements interfaces: +- [IServiceInstance](../bca-token-solidity/contracts/Iface_ServiceInstance.sol) +- [ReentrancyGuard](https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard) + +The contract has public fields: +- `IERC20 public immutable tokToken` + * the common ERC20/stable-coin +- `uint256 public immutable dayPrice` + * the price of the service for 24 hours; minimizes rounding errors +- `address public immutable providerAddress` + * the address of the provider +- `address public immutable userAddress` + * the address of the user +- `uint256 public deposit` + * the amount of deposits made by the user +- `uint256 public retracted` + * the amount of withdrawals made by the provider +- `uint256 public startTime` + * when the contract has been started +- `uint256 public endTime` + * when it has been stopped + +The contract has public methods: +- `constructor(address setProviderAddress, address tokAddress, + address setUserAddress, + uint256 setDayPrice)` +- `function makeDeposit(uint256 amount) external nonReentrant` + * deposits can only be made from the user's address +- `function stop() external nonReentrant` + * both parties may call stop() at any time +- `function balanceUser() public view returns (uint256)` + * calculates the user's balance +- `function balanceProvider() public view returns (uint256)` + * calculates the provider's balance +- `function withdrawUser(uint256 amount) external nonReentrant` + * the user may withdraw from his balance; this will affect the deposit +- `function withdrawProvider(uint256 amount) external nonReentrant` + * the provider may withdraw up to his calculated balance + + +## Contract: BCAServiceFunding24 + +This contract automates the funding of a service contract by transferring the required funds to run the service every 24 hours. The contract is guaranteed to only transfer the said amount at most every 24 hours. It requires an external transaction to `deposit()` which is sent from a bot. + +Source file: [BCA_Funding24.sol](../bca-token-solidity/contracts/BCA_Funding24.sol) + +Implements interfaces: +- [IFunding24](../bca-token-solidity/contracts/Iface_Funding24.sol) +- [Ownable](https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable) + +The contract has public fields: +- `address public immutable targetContract` + * the target contract +- `uint256 public immutable dailyAmount` + * the amount to deposit every 24 hours +- `IERC20 public immutable token` + * the common ERC20/stable-coin +- `uint256 public lastDepositTime` + * remembers the last time a deposit was made + +The contract has public methods: +- `constructor( + address initialOwner, + address setTargetContract, + address setToken, + uint256 setDailyAmount` + * the parameterization of the contract is done in the constructor. The owner is set to the service user. +- `function deposit() external` + * this method triggers the transfer of funds and makes the deposit to the service contract +- `function canDeposit() external view returns (bool)` + * returns true if a deposit can be make \ No newline at end of file diff --git a/doc/Deployment.md b/doc/Deployment.md new file mode 100644 index 0000000..b5b08e2 --- /dev/null +++ b/doc/Deployment.md @@ -0,0 +1,30 @@ +# Deployment + +Depending on the target chain, we run for deployment of the contracts: + +## Deploy to local node + +First, run a node in another terminal: +```sh +cd bca-token-solidity +npx hardhat compile +npx hardhat node +``` + +* if this our first run of the node, then we can import the list of output accounts into MetaMask +* in other cases it might be necessary to "clear activity" in MetaMask for these accounts so it resyncs with the node and starts with the first block + +Then, deploy the ERC20 contract along with the service manager contract: + +```sh +npx hardhat ignition deploy ignition/modules/BCA_Token.ts --network localhost +npx hardhat ignition deploy ignition/modules/BCA_ServiceManager.ts --network localhost +``` + +## Deploy to Amoy testnet + +- tbd + +## Deploy to Polygon mainnet + +- tbd diff --git a/doc/README.md b/doc/README.md index 242bc28..f670972 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,6 +5,11 @@ [Service Tokenization](./Tokenization.md) +## The smart contracts + +[Smart contracts](./Contracts.md) + + ## The transactions involved [Transaction Flow](./Flow.md) @@ -14,6 +19,13 @@ [Services](./Services.md) +## Testing and estimating Gas costs + +[Testing](./Testing.md) + +## Deployment + +[Deployment](./Deployment.md) ## User authentication diff --git a/doc/Testing.md b/doc/Testing.md new file mode 100644 index 0000000..f23ebcd --- /dev/null +++ b/doc/Testing.md @@ -0,0 +1,102 @@ +# Testing + +Tests are implemented in [bca-token-solidity/test](./bca-token-solidity/test/) and can be run: + +```sh +cd bca-token-solidity +npx hardhat test +``` + +# Test coverage + +How much of the contract's code is covered by tests? This can be answered by: + +```sh +cd bca-token-solidity +npx hardhat coverage +``` + +Which outputs as of today (2024-11-23): + +```sh +------------------------------|----------|----------|----------|----------|----------------| +File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | +------------------------------|----------|----------|----------|----------|----------------| + contracts/ | 88.66 | 71.82 | 89.29 | 91.37 | | + BCA_ERC20_nf.sol | 100 | 68.75 | 100 | 100 | | + BCA_Funding24.sol | 84.62 | 81.25 | 100 | 88.89 | 64,66 | + BCA_Service.sol | 25 | 25 | 33.33 | 46.15 |... 45,48,50,54 | + BCA_ServiceController.sol | 85.71 | 50 | 66.67 | 88.89 | 43 | + BCA_ServiceInstance.sol | 95.45 | 81.48 | 100 | 96.97 | 141,171 | + BCA_ServiceManager.sol | 100 | 60 | 100 | 100 | | + Iface_Funding24.sol | 100 | 100 | 100 | 100 | | + Iface_Service.sol | 100 | 100 | 100 | 100 | | + Iface_ServiceController.sol | 100 | 100 | 100 | 100 | | + Iface_ServiceInstance.sol | 100 | 100 | 100 | 100 | | + Iface_ServiceManager.sol | 100 | 100 | 100 | 100 | | +------------------------------|----------|----------|----------|----------|----------------| +All files | 88.66 | 71.82 | 89.29 | 91.37 | | +------------------------------|----------|----------|----------|----------|----------------| +``` + +Clearly, for some of the contracts we have to write some more tests to increase coverage. + +# Estimating Gas costs + +If we run the tests with the environment variable `REPORT_GAS=true`, then the output will contain information about the Gas usage of the transactions. + +```sh +cd bca-token-solidity +REPORT_GAS=true npx hardhat test +``` + +Will output: +```sh +·----------------------------------------------|---------------------------|--------------|-----------------------------· +| Solc version: 0.8.24 · Optimizer enabled: true · Runs: 1000 · Block limit: 30000000 gas │ +···············································|···························|··············|······························ +| Methods │ +·························|·····················|·············|·············|··············|···············|·············· +| Contract · Method · Min · Max · Avg · # calls · usd (avg) │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceController · newService · - · - · 1106858 · 4 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceFunding24 · deposit · 100100 · 156994 · 138029 · 12 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceInstance · makeDeposit · 51323 · 105528 · 101656 · 28 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceInstance · stop · - · - · 49123 · 4 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceInstance · withdrawProvider · 70814 · 105014 · 87939 · 9 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceInstance · withdrawUser · - · - · 73013 · 2 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceManager · newController · 1385995 · 1402892 · 1394444 · 4 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · approve · 46388 · 46448 · 46417 · 25 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · burn · 33963 · 33975 · 33971 · 6 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · mint · 36620 · 70856 · 61634 · 26 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · setBurnerAddress · - · - · 56508 · 1 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · setMinterAddress · - · - · 56486 · 1 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · setServiceAddress · - · - · 54551 · 2 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · transfer · 29706 · 51582 · 45948 · 13 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| BCAServiceToken · transferFrom · - · - · 57616 · 3 · - │ +·························|·····················|·············|·············|··············|···············|·············· +| Deployments · · % of limit · │ +···············································|·············|·············|··············|···············|·············· +| BCAServiceFunding24 · - · - · 854510 · 2.8 % · - │ +···············································|·············|·············|··············|···············|·············· +| BCAServiceInstance · - · - · 769313 · 2.6 % · - │ +···············································|·············|·············|··············|···············|·············· +| BCAServiceManager · - · - · 1781152 · 5.9 % · - │ +···············································|·············|·············|··············|···············|·············· +| BCAServiceToken · - · - · 1175977 · 3.9 % · - │ +·----------------------------------------------|-------------|-------------|--------------|---------------|-------------· +``` diff --git a/doc/img/service_contract.png b/doc/img/service_contract.png new file mode 100644 index 0000000..6bd9596 Binary files /dev/null and b/doc/img/service_contract.png differ diff --git a/doc/img/service_micropayment.png b/doc/img/service_micropayment.png new file mode 100644 index 0000000..def5e88 Binary files /dev/null and b/doc/img/service_micropayment.png differ