From f63adce7b0ec3fd8373fbc06f12e6109249404e2 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Thu, 14 Nov 2024 21:35:12 +0100 Subject: [PATCH 01/12] contract for funding services at most every 24h Signed-off-by: Alexander Diemand --- .../contracts/BCA_Funding24.sol | 69 +++++ bca-token-solidity/test/BCA_Funding24.ts | 261 ++++++++++++++++++ bca-token-solidity/test/BCA_Service.ts | 5 +- 3 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 bca-token-solidity/contracts/BCA_Funding24.sol create mode 100644 bca-token-solidity/test/BCA_Funding24.ts diff --git a/bca-token-solidity/contracts/BCA_Funding24.sol b/bca-token-solidity/contracts/BCA_Funding24.sol new file mode 100644 index 0000000..6be1308 --- /dev/null +++ b/bca-token-solidity/contracts/BCA_Funding24.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract BCAServiceFunding24 is Ownable { + address public immutable targetContract; + uint256 public immutable dailyAmount; + uint256 public lastDepositTime; + IERC20 public immutable token; + + event DepositMade(uint256 amount, uint256 timestamp); + + constructor( + address _initialOwner, + address _targetContract, + address _token, + uint256 _dailyAmount + ) Ownable(_initialOwner) { + require(_targetContract != address(0), "Invalid target contract address"); + require(_token != address(0), "Invalid token address"); + require(_dailyAmount > 0, "Invalid daily amount"); + + targetContract = _targetContract; + token = IERC20(_token); + dailyAmount = _dailyAmount; + } + + function deposit() external { + require( + block.timestamp >= lastDepositTime + 24 hours, + "24 hours have not passed since last deposit" + ); + + // Check if owner has sufficient balance and has approved this contract + require( + token.balanceOf(owner()) >= dailyAmount, + "Insufficient balance in owner's wallet" + ); + require( + token.allowance(owner(), address(this)) >= dailyAmount, + "Insufficient allowance from owner" + ); + + // Transfer tokens from owner to this contract + token.transferFrom(owner(), address(this), dailyAmount); + + // Set allowance for the target contract + token.approve(targetContract, dailyAmount); + + // Call deposit function on target contract + (bool success, ) = targetContract.call( + abi.encodeWithSignature("makeDeposit(uint256)", dailyAmount) + ); + require(success, "Deposit failed"); + + lastDepositTime = block.timestamp; + + emit DepositMade(dailyAmount, block.timestamp); + } + + // Function to check if deposit is currently possible + function canDeposit() external view returns (bool) { + return block.timestamp >= lastDepositTime + 24 hours && + token.balanceOf(owner()) >= dailyAmount && + token.allowance(owner(), address(this)) >= dailyAmount; + } +} \ No newline at end of file diff --git a/bca-token-solidity/test/BCA_Funding24.ts b/bca-token-solidity/test/BCA_Funding24.ts new file mode 100644 index 0000000..df4d739 --- /dev/null +++ b/bca-token-solidity/test/BCA_Funding24.ts @@ -0,0 +1,261 @@ +import { + time, + loadFixture, + } from "@nomicfoundation/hardhat-toolbox/network-helpers"; + import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; + import { expect } from "chai"; + import hre from "hardhat"; + + describe("BCA Funding", function () { + // We define a fixture to reuse the same setup in every test. + // We use loadFixture to run this setup once, snapshot that state, + // and reset Hardhat Network to that snapshot in every test. + async function deployContract() { + // Contracts are deployed using the first signer/account by default + const [owner, minter, burner, provider, user1, user2] = await hre.ethers.getSigners(); + + const Token = await hre.ethers.getContractFactory("BCAServiceToken"); + const tokenContract = await Token.deploy("Test token", "TOK1", minter, burner); + const precision: bigint = await tokenContract.decimals().then(d => { if (d == 0n) {return 18n;} else {return d}; }); + + const Contract1 = await hre.ethers.getContractFactory("BCAServiceContract"); + const daily_price = 1n; // 1 token per 24h + const day_price = (daily_price * BigInt(10n**precision)); + const serviceContract = await Contract1.deploy(provider, tokenContract.getAddress(), day_price); + + const Contract2 = await hre.ethers.getContractFactory("BCAServiceFunding24"); + const daily_funds = 101n; // 1.01 token per 24h + const day_funds = (daily_funds * BigInt(10n**precision) / 100n); + const target_contract = serviceContract.getAddress(); + const fundingContract = await Contract2.deploy(user1, target_contract, tokenContract.getAddress(), day_funds); + + // minting some tokens to the users + const one_token = 1n * BigInt(10n**precision); + expect(await tokenContract.connect(minter).mint(user1.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user1, 100n*one_token); + expect(await tokenContract.connect(minter).mint(user2.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user2, 10n*one_token); + + const startblocktime: bigint = BigInt(await time.increase(30)); + + return { token: { tokenContract, one_token, owner, minter, burner, provider, user1, user2 }, + funding: { fundingContract, day_funds }, + service: { serviceContract, provider, user1, user2 } }; + } + + describe("Deployment", function () { + it("Funding contract: should set the right target contract", async function () { + const { funding, service } = await loadFixture(deployContract); + expect(await funding.fundingContract.targetContract()).to.equal( + await service.serviceContract.getAddress() + ); + }); + + it("Funding contract: should set the right daily amount", async function () { + const { funding } = await loadFixture(deployContract); + expect(await funding.fundingContract.dailyAmount()).to.equal( + funding.day_funds + ); + }); + + it("Funding contract: should set the right token", async function () { + const { token, funding } = await loadFixture(deployContract); + expect(await funding.fundingContract.token()).to.equal( + await token.tokenContract.getAddress() + ); + }); + }); + + describe("Deposit functionality", function () { + it("Should allow deposit when properly funded and approved", async function () { + const { token, funding } = await loadFixture(deployContract); + + // Approve funding contract to spend user1's tokens + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds + ); + + // Initial deposit should succeed + await expect(funding.fundingContract.connect(token.user1).deposit()) + .to.emit(funding.fundingContract, "DepositMade") + .withArgs(funding.day_funds, anyValue); + }); + + it("Should revert if user has insufficient balance for allowance", async function () { + const { token, funding } = await loadFixture(deployContract); + + // Transfer all tokens from user2 to user1 + await token.tokenContract.connect(token.user2).transfer( + token.user1.address, + await token.tokenContract.balanceOf(token.user2.address) + ); + + await token.tokenContract.connect(token.user2).approve( + funding.fundingContract.getAddress(), + funding.day_funds + ); + + await expect( + funding.fundingContract.connect(token.user2).deposit() + ).to.be.revertedWith("Insufficient allowance from owner"); + }); + + it("Should revert if allowance is insufficient", async function () { + const { token, funding } = await loadFixture(deployContract); + + // Approve one less than required amount + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds - 1n + ); + + await expect( + funding.fundingContract.connect(token.user1).deposit() + ).to.be.revertedWith("Insufficient allowance from owner"); + }); + + it("Should revert if called twice within 24 hours", async function () { + const { token, funding } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds * 2n + ); + + // First deposit + await funding.fundingContract.connect(token.user1).deposit(); + + // Second deposit should fail + await expect( + funding.fundingContract.connect(token.user1).deposit() + ).to.be.revertedWith("24 hours have not passed since last deposit"); + }); + + it("Should allow deposit after 24 hours", async function () { + const { token, funding } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds * 2n + ); + + // First deposit + await funding.fundingContract.connect(token.user1).deposit(); + + // Increase time by 24 hours + await time.increase(24 * 60 * 60); + + // Second deposit should succeed + await expect( + funding.fundingContract.connect(token.user1).deposit() + ).to.not.be.reverted; + }); + }); + + describe("canDeposit functionality", function () { + it("Should return true when all conditions are met", async function () { + const { token, funding } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds + ); + + expect(await funding.fundingContract.connect(token.user1).canDeposit()) + .to.be.true; + }); + + it("Should return false right after a deposit", async function () { + const { token, funding } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds * 2n + ); + + await funding.fundingContract.connect(token.user1).deposit(); + + expect(await funding.fundingContract.connect(token.user1).canDeposit()) + .to.be.false; + }); + + it("Should return true again after 24 hours", async function () { + const { token, funding } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds * 2n + ); + + await funding.fundingContract.connect(token.user1).deposit(); + await time.increase(24 * 60 * 60); + + expect(await funding.fundingContract.connect(token.user1).canDeposit()) + .to.be.true; + }); + }); + + describe("Integration with service contract", function () { + it("Should successfully deposit to service contract", async function () { + const { token, funding, service } = await loadFixture(deployContract); + + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds + ); + + const initialBalance = await token.tokenContract.balanceOf( + service.serviceContract.getAddress() + ); + + await funding.fundingContract.connect(token.user1).deposit(); + + // advance time and create a new block + const block1 = BigInt(await time.increase(30)); + + // the start time must be registered on the first succesfull call to "makeDeposit" + expect(await service.serviceContract.startTime()).to.lessThanOrEqual(block1, "not right"); + + expect(await token.tokenContract.balanceOf( + service.serviceContract.getAddress() + )).to.equal(initialBalance + funding.day_funds); + + }); + + it("Should correctly handle the entire flow", async function () { + const { token, funding, service } = await loadFixture(deployContract); + + // Initial approval + await token.tokenContract.connect(token.user1).approve( + funding.fundingContract.getAddress(), + funding.day_funds * 3n + ); + + // First deposit + await funding.fundingContract.connect(token.user1).deposit(); + + // advance time and create a new block + const block1 = BigInt(await time.increase(30)); + + // Try immediate deposit (should fail) + await expect( + funding.fundingContract.connect(token.user1).deposit() + ).to.be.revertedWith("24 hours have not passed since last deposit"); + + // Wait 24 hours + await time.increase(24 * 60 * 60); + + // Second deposit should succeed + await expect( + funding.fundingContract.connect(token.user1).deposit() + ).to.not.be.reverted; + + // the start time must be registered on the first succesfull call to "deposit" + expect(await service.serviceContract.startTime()).to.lessThanOrEqual(block1, "not right"); + + // Verify final balance in service contract + expect(await token.tokenContract.balanceOf( + service.serviceContract.getAddress() + )).to.equal(funding.day_funds * 2n); + }); + }); +}); diff --git a/bca-token-solidity/test/BCA_Service.ts b/bca-token-solidity/test/BCA_Service.ts index 4bc147b..3ff76ab 100644 --- a/bca-token-solidity/test/BCA_Service.ts +++ b/bca-token-solidity/test/BCA_Service.ts @@ -22,7 +22,7 @@ import { const daily_price = 1n; // 1 token per 24h const day_price = (daily_price * BigInt(10n**precision)); assert(day_price > 0n, "tick price must be > 0: " + (day_price.toString())); - console.log(`tick price: ${day_price}`) + // console.log(`tick price: ${day_price}`) const serviceContract = await Contract.deploy(provider, tokenContract.getAddress(), day_price); // minting some tokens to the users @@ -118,6 +118,7 @@ import { await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); await expect(service.serviceContract.connect(service.user1).makeDeposit(deposit)).to.be.revertedWithCustomError(service.serviceContract, 'InsufficientAmount') }); + it("The user deposits to the contract and thus starts the service", async function () { const { token, service } = await loadFixture(deployContract); @@ -205,7 +206,7 @@ import { // check the user's balance: should be ~ 0.5; will pay a tick until next tx const ubal = await service.serviceContract.connect(service.user1).balanceUser() - (await service.serviceContract.dayPrice() / 24n / 3600n); const rem = await service.serviceContract.deposit() - console.log(`user's balance: ${ubal} deposit: ${rem} 24h price: ${await service.serviceContract.dayPrice()}`) + // console.log(`user's balance: ${ubal} deposit: ${rem} 24h price: ${await service.serviceContract.dayPrice()}`) // the user withdraws from the contract which stops the service expect(await service.serviceContract.connect(service.user1).withdrawUser(ubal)).to.emit(service.serviceContract, "ServiceStopped").withArgs('ticks'); From 0ef472777f3ad143e69b6163ee3ce1e0d89f8df4 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Mon, 18 Nov 2024 23:13:27 +0100 Subject: [PATCH 02/12] +factories; +interfaces Signed-off-by: Alexander Diemand --- .../contracts/BCA_Funding24.sol | 39 ++-- bca-token-solidity/contracts/BCA_Service.sol | 168 +++--------------- .../contracts/BCA_ServiceInstance.sol | 160 +++++++++++++++++ .../contracts/BCA_ServiceManager.sol | 42 +++++ .../contracts/Iface_Funding24.sol | 6 + .../contracts/Iface_Service.sol | 6 + .../contracts/Iface_ServiceInstance.sol | 9 + .../contracts/Iface_ServiceManager.sol | 6 + .../ignition/deploy_services-example.json | 7 - .../ignition/deploy_services.json | 17 -- .../ignition/modules/BCA_Service.ts | 27 --- .../ignition/modules/BCA_ServiceManager.ts | 20 +++ bca-token-solidity/test/BCA_Funding24.ts | 57 +++--- ...{BCA_Service.ts => BCA_ServiceInstance.ts} | 40 ++--- 14 files changed, 338 insertions(+), 266 deletions(-) create mode 100644 bca-token-solidity/contracts/BCA_ServiceInstance.sol create mode 100644 bca-token-solidity/contracts/BCA_ServiceManager.sol create mode 100644 bca-token-solidity/contracts/Iface_Funding24.sol create mode 100644 bca-token-solidity/contracts/Iface_Service.sol create mode 100644 bca-token-solidity/contracts/Iface_ServiceInstance.sol create mode 100644 bca-token-solidity/contracts/Iface_ServiceManager.sol delete mode 100644 bca-token-solidity/ignition/deploy_services-example.json delete mode 100644 bca-token-solidity/ignition/deploy_services.json delete mode 100644 bca-token-solidity/ignition/modules/BCA_Service.ts create mode 100644 bca-token-solidity/ignition/modules/BCA_ServiceManager.ts rename bca-token-solidity/test/{BCA_Service.ts => BCA_ServiceInstance.ts} (90%) diff --git a/bca-token-solidity/contracts/BCA_Funding24.sol b/bca-token-solidity/contracts/BCA_Funding24.sol index 6be1308..3dc9543 100644 --- a/bca-token-solidity/contracts/BCA_Funding24.sol +++ b/bca-token-solidity/contracts/BCA_Funding24.sol @@ -2,15 +2,20 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import "./Iface_Funding24.sol"; +import "./Iface_ServiceInstance.sol"; + +contract BCAServiceFunding24 is Iface_Funding24, Ownable { + using SafeERC20 for IERC20; -contract BCAServiceFunding24 is Ownable { address public immutable targetContract; uint256 public immutable dailyAmount; - uint256 public lastDepositTime; IERC20 public immutable token; + uint256 public lastDepositTime; - event DepositMade(uint256 amount, uint256 timestamp); + event DepositMade(address targetContract, uint256 amount, uint256 timestamp); constructor( address _initialOwner, @@ -25,11 +30,12 @@ contract BCAServiceFunding24 is Ownable { targetContract = _targetContract; token = IERC20(_token); dailyAmount = _dailyAmount; + lastDepositTime = 0; } function deposit() external { require( - block.timestamp >= lastDepositTime + 24 hours, + lastDepositTime == 0 || block.timestamp >= lastDepositTime + 24 hours, "24 hours have not passed since last deposit" ); @@ -43,26 +49,25 @@ contract BCAServiceFunding24 is Ownable { "Insufficient allowance from owner" ); - // Transfer tokens from owner to this contract - token.transferFrom(owner(), address(this), dailyAmount); + // // Transfer tokens from owner to this contract + token.safeTransferFrom(owner(), address(this), dailyAmount); - // Set allowance for the target contract + // // Set allowance for the target contract token.approve(targetContract, dailyAmount); - // Call deposit function on target contract - (bool success, ) = targetContract.call( - abi.encodeWithSignature("makeDeposit(uint256)", dailyAmount) - ); - require(success, "Deposit failed"); - - lastDepositTime = block.timestamp; - - emit DepositMade(dailyAmount, block.timestamp); + try Iface_ServiceInstance(targetContract).makeDeposit(dailyAmount) { + lastDepositTime = block.timestamp; + emit DepositMade(targetContract, dailyAmount, block.timestamp); + } catch Error(string memory reason) { + revert(reason); + } catch (bytes memory) { + revert("failed to make deposit"); + } } // Function to check if deposit is currently possible function canDeposit() external view returns (bool) { - return block.timestamp >= lastDepositTime + 24 hours && + return (lastDepositTime == 0 || block.timestamp >= lastDepositTime + 24 hours) && token.balanceOf(owner()) >= dailyAmount && token.allowance(owner(), address(this)) >= dailyAmount; } diff --git a/bca-token-solidity/contracts/BCA_Service.sol b/bca-token-solidity/contracts/BCA_Service.sol index 9fd22fd..42693e6 100644 --- a/bca-token-solidity/contracts/BCA_Service.sol +++ b/bca-token-solidity/contracts/BCA_Service.sol @@ -4,164 +4,50 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./Iface_Service.sol"; +import "./BCA_ServiceInstance.sol"; -contract BCAServiceContract is ReentrancyGuard { +contract BCAService is Iface_Service, ReentrancyGuard { using SafeERC20 for IERC20; - // the price for launching the contract; will remain with the contract - // adjust for the various transaction costs depending on chain - uint256 public constant setupFee = 2 * 10 ** 17; - // will be set in the constructor IERC20 public immutable tokToken; uint256 public immutable dayPrice; address public immutable providerAddress; - uint256 public deposit; - uint256 public retracted; - uint256 public startTime; - uint256 public endTime; - address public userAddress; + uint16 public immutable maxInstances; + + address[] public deployedInstances; - event DepositMade(address user, uint256 amount); - event ServiceStopped(uint256 ticks); - event Withdrawn(address recipient, uint256 amount); - event Retracted(address recipient, uint256 amount); + // Event to notify when a new instance is deployed + event InstanceDeployed(address contractAddress); - error AlreadySubscribed(); - error AlreadyStopped(); - error InsufficientAmount(uint256 minimumAmount); - error InsufficientBalance(uint256 availableBalance); - error NotStarted(); - error UnAuthorized(); + error Exhausted(); - constructor(address _providerAddress, address _tokAddress, uint256 _dayPrice) { + constructor(address _providerAddress, address _tokAddress, + uint16 _maxInstances, uint256 _dayPrice) { tokToken = IERC20(_tokAddress); dayPrice = _dayPrice; providerAddress = _providerAddress; + maxInstances = _maxInstances; } - function makeDeposit(uint256 amount) external nonReentrant { - // require(amount > 0, "Deposit must be greater than 0"); by type! - if (! (userAddress == address(0) || userAddress == msg.sender)) { - revert AlreadySubscribed(); - } - - // first deposit pays the setup fee - if (userAddress == address(0) && amount < (setupFee + (dayPrice / 24))) { - revert InsufficientAmount(setupFee + (dayPrice / 24)); - } - - // cannot wake up the service once it has stopped - if (endTime != 0) { - revert AlreadyStopped(); - } - - // allowance for the amount must have been approved by the sender - tokToken.safeTransferFrom(msg.sender, address(this), amount); - deposit += amount; - - // remember user who made first deposit (e.g. subscribed) - if (userAddress == address(0)) { - userAddress = msg.sender; - startTime = block.timestamp; - deposit -= setupFee; // pay setup fee - } - emit DepositMade(msg.sender, amount); - } - - function stop() external { - _stop(msg.sender); - } - - function _stop(address from) private { - if (! (from == userAddress || from == providerAddress)) { - revert UnAuthorized(); - } - if (endTime != 0) { - revert AlreadyStopped(); - } - - endTime = block.timestamp; - uint256 ticks = endTime - startTime; - emit ServiceStopped(ticks); - } - - function balanceUser() public view returns (uint256) { - if (userAddress == address(0) || startTime == 0) { - revert NotStarted(); - } - if (msg.sender != userAddress) { - revert UnAuthorized(); - } - - uint256 calcTime = endTime != 0 ? endTime : block.timestamp; - uint256 duration = calcTime - startTime; - uint256 paid = duration * dayPrice / 24 / 3600; - - if (paid >= deposit) { - return 0; - } else { - return deposit - paid; - } - } - - function balanceProvider() public view returns (uint256) { - if (msg.sender != providerAddress) { - revert UnAuthorized(); - } - if (startTime == 0) { - revert NotStarted(); + function newInstance(address _userAddress) external nonReentrant returns (address) { + if (deployedInstances.length >= maxInstances) { + revert Exhausted(); } - - uint256 calcTime = endTime != 0 ? endTime : block.timestamp; - uint256 ticks = calcTime - startTime; - uint256 balance = 0; - uint256 bal1 = ticks * dayPrice / 24 / 3600; - // cap the available balance - if (bal1 > deposit - retracted) { - balance = deposit - retracted; - } else { - balance = bal1; - } - return balance; + // Create a new SimpleContract + BCAServiceInstance instanceContract = new BCAServiceInstance(providerAddress, address(tokToken), _userAddress, dayPrice); + + // Store the address + deployedInstances.push(address(instanceContract)); + + // Emit event + emit InstanceDeployed(address(instanceContract)); + + return address(instanceContract); } - function withdrawUser(uint256 amount) external nonReentrant { - if (msg.sender != userAddress) { - revert UnAuthorized(); - } - uint256 balance = balanceUser(); - if (balance < amount) { - revert InsufficientBalance(balance); - } - - deposit -= amount; - tokToken.transfer(msg.sender, amount); - emit Withdrawn(msg.sender, amount); - - // stop service? - if (balance - amount <= dayPrice / 24 / 3600) { - _stop(msg.sender); - } - } - - function withdrawProvider(uint256 amount) external nonReentrant { - if (msg.sender != providerAddress) { - revert UnAuthorized(); - } - - uint256 balance = balanceProvider(); - if (balance < amount) { - revert InsufficientBalance(balance); - } - - // stop service? - if (deposit - retracted - amount < dayPrice / 24 / 3600) { - _stop(msg.sender); - } - - retracted += amount; - tokToken.transfer(msg.sender, amount); - emit Retracted(msg.sender, amount); + function countServiceInstances() public view returns (uint) { + return deployedInstances.length; } } \ No newline at end of file diff --git a/bca-token-solidity/contracts/BCA_ServiceInstance.sol b/bca-token-solidity/contracts/BCA_ServiceInstance.sol new file mode 100644 index 0000000..225991c --- /dev/null +++ b/bca-token-solidity/contracts/BCA_ServiceInstance.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./Iface_ServiceInstance.sol"; + +contract BCAServiceInstance is Iface_ServiceInstance, ReentrancyGuard { + using SafeERC20 for IERC20; + + // will be set in the constructor + IERC20 public immutable tokToken; + uint256 public immutable dayPrice; + address public immutable providerAddress; + address public immutable userAddress; + + uint256 public deposit; + uint256 public retracted; + uint256 public startTime; + uint256 public endTime; + + event DepositMade(address user, uint256 amount); + event ServiceStopped(uint256 ticks); + event Withdrawn(address recipient, uint256 amount); + event Retracted(address recipient, uint256 amount); + + // error AlreadySubscribed(); + error AlreadyStopped(); + // error InsufficientAmount(uint256 minimumAmount); + error InsufficientBalance(uint256 availableBalance); + error NotStarted(); + error UnAuthorized(); + + constructor(address _providerAddress, address _tokAddress, + address _userAddress, + uint256 _dayPrice) { + tokToken = IERC20(_tokAddress); + dayPrice = _dayPrice; + providerAddress = _providerAddress; + userAddress = _userAddress; + } + + function makeDeposit(uint256 amount) external nonReentrant { + // if (userAddress != msg.sender) { + // revert AlreadySubscribed(); + // } + + // cannot wake up the service once it has stopped + if (endTime != 0) { + revert AlreadyStopped(); + } + + // allowance for the amount must have been approved by the sender + tokToken.safeTransferFrom(msg.sender, address(this), amount); + deposit += amount; + + // start contract on first deposit + if (startTime == 0) { + startTime = block.timestamp; + } + emit DepositMade(msg.sender, amount); + } + + function stop() external nonReentrant { + _stop(msg.sender); + } + + function _stop(address from) private { + if (! (from == userAddress || from == providerAddress)) { + revert UnAuthorized(); + } + if (endTime != 0) { + revert AlreadyStopped(); + } + + endTime = block.timestamp; + uint256 ticks = endTime - startTime; + emit ServiceStopped(ticks); + } + + function balanceUser() public view returns (uint256) { + if (startTime == 0) { + revert NotStarted(); + } + if (msg.sender != userAddress) { + revert UnAuthorized(); + } + + uint256 calcTime = endTime != 0 ? endTime : block.timestamp; + uint256 duration = calcTime - startTime; + uint256 paid = duration * dayPrice / 24 / 3600; + + if (paid >= deposit) { + return 0; + } else { + return deposit - paid; + } + } + + function balanceProvider() public view returns (uint256) { + if (msg.sender != providerAddress) { + revert UnAuthorized(); + } + if (startTime == 0) { + revert NotStarted(); + } + + uint256 calcTime = endTime != 0 ? endTime : block.timestamp; + uint256 ticks = calcTime - startTime; + uint256 balance = 0; + uint256 bal1 = ticks * dayPrice / 24 / 3600; + // cap the available balance + if (bal1 > deposit - retracted) { + balance = deposit - retracted; + } else { + balance = bal1; + } + return balance; + } + + function withdrawUser(uint256 amount) external nonReentrant { + if (msg.sender != userAddress) { + revert UnAuthorized(); + } + uint256 balance = balanceUser(); + if (balance < amount) { + revert InsufficientBalance(balance); + } + + tokToken.transfer(msg.sender, amount); + deposit -= amount; + emit Withdrawn(msg.sender, amount); + + // stop service? + if (balance - amount <= dayPrice / 24 / 3600) { + _stop(msg.sender); + } + } + + function withdrawProvider(uint256 amount) external nonReentrant { + if (msg.sender != providerAddress) { + revert UnAuthorized(); + } + + uint256 balance = balanceProvider(); + if (balance < amount) { + revert InsufficientBalance(balance); + } + + // stop service? + if (deposit - retracted - amount < dayPrice / 24 / 3600) { + _stop(msg.sender); + } + + tokToken.transfer(msg.sender, amount); + retracted += amount; + emit Retracted(msg.sender, amount); + } +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/BCA_ServiceManager.sol b/bca-token-solidity/contracts/BCA_ServiceManager.sol new file mode 100644 index 0000000..5941740 --- /dev/null +++ b/bca-token-solidity/contracts/BCA_ServiceManager.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./Iface_ServiceManager.sol"; +import "./BCA_Service.sol"; + +// Factory contract that deploys service contracts +contract BCAServiceManager is Iface_ServiceManager, ReentrancyGuard { + using SafeERC20 for IERC20; + + IERC20 public immutable tokToken; + address public immutable providerAddress; + address[] public deployedServices; + + // Event to notify when a new service is deployed + event ServiceDeployed(address contractAddress); + + constructor(address _providerAddress, address _tokAddress) { + tokToken = IERC20(_tokAddress); + providerAddress = _providerAddress; + } + + function newService(uint16 _maxInstances, uint256 _dayPrice) external nonReentrant returns (address) { + // Create a new SimpleContract + BCAService serviceContract = new BCAService(providerAddress, address(tokToken), _maxInstances, _dayPrice); + + // Store the address + deployedServices.push(address(serviceContract)); + + // Emit event + emit ServiceDeployed(address(serviceContract)); + + return address(serviceContract); + } + + function countServiceContracts() public view returns (uint) { + return deployedServices.length; + } +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_Funding24.sol b/bca-token-solidity/contracts/Iface_Funding24.sol new file mode 100644 index 0000000..9f2af91 --- /dev/null +++ b/bca-token-solidity/contracts/Iface_Funding24.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +interface Iface_Funding24 { + function deposit() external; +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_Service.sol b/bca-token-solidity/contracts/Iface_Service.sol new file mode 100644 index 0000000..668c401 --- /dev/null +++ b/bca-token-solidity/contracts/Iface_Service.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +interface Iface_Service { + function newInstance(address _userAddress) external returns (address); +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_ServiceInstance.sol b/bca-token-solidity/contracts/Iface_ServiceInstance.sol new file mode 100644 index 0000000..652b81f --- /dev/null +++ b/bca-token-solidity/contracts/Iface_ServiceInstance.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +interface Iface_ServiceInstance { + function makeDeposit(uint256 amount) external; + function stop() external; + function withdrawUser(uint256 amount) external; + function withdrawProvider(uint256 amount) external; +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_ServiceManager.sol b/bca-token-solidity/contracts/Iface_ServiceManager.sol new file mode 100644 index 0000000..38a6a13 --- /dev/null +++ b/bca-token-solidity/contracts/Iface_ServiceManager.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +interface Iface_ServiceManager { + function newService(uint16 _maxInstances, uint256 _dayPrice) external returns (address); +} \ No newline at end of file diff --git a/bca-token-solidity/ignition/deploy_services-example.json b/bca-token-solidity/ignition/deploy_services-example.json deleted file mode 100644 index 1417802..0000000 --- a/bca-token-solidity/ignition/deploy_services-example.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "name": "test service at 2 BCA1 per day", - "providerAddress": "0xbe371e774e8b0c87912eb64be1e2851c5b702b38", - "dayPrice": "2000000000000000000" - } -] \ No newline at end of file diff --git a/bca-token-solidity/ignition/deploy_services.json b/bca-token-solidity/ignition/deploy_services.json deleted file mode 100644 index d996428..0000000 --- a/bca-token-solidity/ignition/deploy_services.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "name": "test service at 2 BCA1 per day", - "providerAddress": "0xBe371e774E8b0c87912Eb64BE1e2851c5b702B38", - "dayPrice": "2000000000000000000" - }, - { - "name": "test service at 1 BCA1 per day", - "providerAddress": "0xBe371e774E8b0c87912Eb64BE1e2851c5b702B38", - "dayPrice": "1000000000000000000" - }, - { - "name": "test service at 3 BCA1 per day", - "providerAddress": "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199", - "dayPrice": "3000000000000000000" - } -] \ No newline at end of file diff --git a/bca-token-solidity/ignition/modules/BCA_Service.ts b/bca-token-solidity/ignition/modules/BCA_Service.ts deleted file mode 100644 index d919cee..0000000 --- a/bca-token-solidity/ignition/modules/BCA_Service.ts +++ /dev/null @@ -1,27 +0,0 @@ -const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); - -import service_contracts from "../deploy_services.json"; - -// local node -import deployed_address from "../deployments/chain-31337/deployed_addresses.json" - -// Amoy testnet -// import deployed_address from "../deployments/chain-80002/deployed_addresses.json" - -const BCAServiceModule = buildModule("BCA_Service", (m) => { - - let bcaservices = [] - let counter = 0; - - service_contracts.forEach(def => { - console.log(` deploying contract ${def.name} with provider=${def.providerAddress}`); - const id = "BCAServiceContract" + ("000" + counter).slice(-4); - const bcaservice = m.contract("BCAServiceContract", [def.providerAddress, deployed_address["BCA_Token#BCAServiceToken"], BigInt(def.dayPrice)], {id}); - bcaservices.push(bcaservice); - counter += 1; - }); - - return { bcaservices }; -}); - -module.exports = BCAServiceModule; diff --git a/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts b/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts new file mode 100644 index 0000000..1f4e32d --- /dev/null +++ b/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts @@ -0,0 +1,20 @@ +const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); + +// local node +import deployed_address from "../deployments/chain-31337/deployed_addresses.json" +// let owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +let provider = "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" + +// Amoy testnet +// import deployed_address from "../deployments/chain-80002/deployed_addresses.json" +// let owner = "0x1A8725f9A4295bb3b4E5321Ecb2c9185004fC76F" +// let provider = "0x480F8D64E9AE32E12C2B537d8347B8E035d43183" + +const BCAServiceManagerModule = buildModule("BCA_ServiceManager", (m) => { + + const bcasrvmgr = m.contract("BCAServiceManager", [provider, deployed_address["BCA_Token#BCAServiceToken"]]); + + return { bcasrvmgr }; + }); + +module.exports = BCAServiceManagerModule; diff --git a/bca-token-solidity/test/BCA_Funding24.ts b/bca-token-solidity/test/BCA_Funding24.ts index df4d739..db91652 100644 --- a/bca-token-solidity/test/BCA_Funding24.ts +++ b/bca-token-solidity/test/BCA_Funding24.ts @@ -12,16 +12,16 @@ import { // and reset Hardhat Network to that snapshot in every test. async function deployContract() { // Contracts are deployed using the first signer/account by default - const [owner, minter, burner, provider, user1, user2] = await hre.ethers.getSigners(); + const [owner, minter, burner, provider, user1, user2, cron] = await hre.ethers.getSigners(); const Token = await hre.ethers.getContractFactory("BCAServiceToken"); const tokenContract = await Token.deploy("Test token", "TOK1", minter, burner); const precision: bigint = await tokenContract.decimals().then(d => { if (d == 0n) {return 18n;} else {return d}; }); - const Contract1 = await hre.ethers.getContractFactory("BCAServiceContract"); + const Contract1 = await hre.ethers.getContractFactory("BCAServiceInstance"); const daily_price = 1n; // 1 token per 24h const day_price = (daily_price * BigInt(10n**precision)); - const serviceContract = await Contract1.deploy(provider, tokenContract.getAddress(), day_price); + const serviceContract = await Contract1.deploy(provider, tokenContract.getAddress(), user1, day_price); const Contract2 = await hre.ethers.getContractFactory("BCAServiceFunding24"); const daily_funds = 101n; // 1.01 token per 24h @@ -37,7 +37,7 @@ import { const startblocktime: bigint = BigInt(await time.increase(30)); return { token: { tokenContract, one_token, owner, minter, burner, provider, user1, user2 }, - funding: { fundingContract, day_funds }, + funding: { fundingContract, day_funds, cron }, service: { serviceContract, provider, user1, user2 } }; } @@ -66,37 +66,38 @@ import { describe("Deposit functionality", function () { it("Should allow deposit when properly funded and approved", async function () { - const { token, funding } = await loadFixture(deployContract); + const { token, funding, service } = await loadFixture(deployContract); // Approve funding contract to spend user1's tokens await token.tokenContract.connect(token.user1).approve( funding.fundingContract.getAddress(), - funding.day_funds + // service.serviceContract.getAddress(), + funding.day_funds * 10n ); // Initial deposit should succeed - await expect(funding.fundingContract.connect(token.user1).deposit()) + await expect(funding.fundingContract.connect(funding.cron).deposit()) .to.emit(funding.fundingContract, "DepositMade") - .withArgs(funding.day_funds, anyValue); + .withArgs(service.serviceContract.getAddress(), funding.day_funds, anyValue); }); it("Should revert if user has insufficient balance for allowance", async function () { const { token, funding } = await loadFixture(deployContract); - // Transfer all tokens from user2 to user1 - await token.tokenContract.connect(token.user2).transfer( - token.user1.address, - await token.tokenContract.balanceOf(token.user2.address) + // Transfer all tokens from user1 to user2 + await token.tokenContract.connect(token.user1).transfer( + token.user2.address, + await token.tokenContract.balanceOf(token.user1.address) ); - await token.tokenContract.connect(token.user2).approve( + await token.tokenContract.connect(token.user1).approve( funding.fundingContract.getAddress(), funding.day_funds ); await expect( - funding.fundingContract.connect(token.user2).deposit() - ).to.be.revertedWith("Insufficient allowance from owner"); + funding.fundingContract.connect(funding.cron).deposit() + ).to.be.revertedWith("Insufficient balance in owner's wallet"); }); it("Should revert if allowance is insufficient", async function () { @@ -109,7 +110,7 @@ import { ); await expect( - funding.fundingContract.connect(token.user1).deposit() + funding.fundingContract.connect(funding.cron).deposit() ).to.be.revertedWith("Insufficient allowance from owner"); }); @@ -122,7 +123,7 @@ import { ); // First deposit - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); // Second deposit should fail await expect( @@ -139,14 +140,14 @@ import { ); // First deposit - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); // Increase time by 24 hours await time.increase(24 * 60 * 60); // Second deposit should succeed await expect( - funding.fundingContract.connect(token.user1).deposit() + funding.fundingContract.connect(funding.cron).deposit() ).to.not.be.reverted; }); }); @@ -160,7 +161,7 @@ import { funding.day_funds ); - expect(await funding.fundingContract.connect(token.user1).canDeposit()) + expect(await funding.fundingContract.connect(funding.cron).canDeposit()) .to.be.true; }); @@ -172,9 +173,9 @@ import { funding.day_funds * 2n ); - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); - expect(await funding.fundingContract.connect(token.user1).canDeposit()) + expect(await funding.fundingContract.connect(funding.cron).canDeposit()) .to.be.false; }); @@ -186,10 +187,10 @@ import { funding.day_funds * 2n ); - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); await time.increase(24 * 60 * 60); - expect(await funding.fundingContract.connect(token.user1).canDeposit()) + expect(await funding.fundingContract.connect(funding.cron).canDeposit()) .to.be.true; }); }); @@ -207,7 +208,7 @@ import { service.serviceContract.getAddress() ); - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); // advance time and create a new block const block1 = BigInt(await time.increase(30)); @@ -231,14 +232,14 @@ import { ); // First deposit - await funding.fundingContract.connect(token.user1).deposit(); + await funding.fundingContract.connect(funding.cron).deposit(); // advance time and create a new block const block1 = BigInt(await time.increase(30)); // Try immediate deposit (should fail) await expect( - funding.fundingContract.connect(token.user1).deposit() + funding.fundingContract.connect(funding.cron).deposit() ).to.be.revertedWith("24 hours have not passed since last deposit"); // Wait 24 hours @@ -246,7 +247,7 @@ import { // Second deposit should succeed await expect( - funding.fundingContract.connect(token.user1).deposit() + funding.fundingContract.connect(funding.cron).deposit() ).to.not.be.reverted; // the start time must be registered on the first succesfull call to "deposit" diff --git a/bca-token-solidity/test/BCA_Service.ts b/bca-token-solidity/test/BCA_ServiceInstance.ts similarity index 90% rename from bca-token-solidity/test/BCA_Service.ts rename to bca-token-solidity/test/BCA_ServiceInstance.ts index 3ff76ab..837d0ca 100644 --- a/bca-token-solidity/test/BCA_Service.ts +++ b/bca-token-solidity/test/BCA_ServiceInstance.ts @@ -18,12 +18,12 @@ import { const tokenContract = await Token.deploy("Test token", "TOK1", minter, burner); const precision: bigint = await tokenContract.decimals().then(d => { if (d == 0n) {return 18n;} else {return d}; }); - const Contract = await hre.ethers.getContractFactory("BCAServiceContract"); + const Contract = await hre.ethers.getContractFactory("BCAServiceInstance"); const daily_price = 1n; // 1 token per 24h const day_price = (daily_price * BigInt(10n**precision)); assert(day_price > 0n, "tick price must be > 0: " + (day_price.toString())); // console.log(`tick price: ${day_price}`) - const serviceContract = await Contract.deploy(provider, tokenContract.getAddress(), day_price); + const serviceContract = await Contract.deploy(provider, tokenContract.getAddress(), user1, day_price); // minting some tokens to the users const one_token = 1n * BigInt(10n**precision); @@ -107,18 +107,6 @@ import { }); describe("Deposit from user", function () { - it("The first deposit starts the service, and must be sufficient", async function () { - const { token, service } = await loadFixture(deployContract); - - let deposit = 1n * token.one_token / 10n; - - expect(await service.serviceContract.startTime()).to.eq(0, "good! not yet started"); - - // approve deposit amounts to be spent by the service contract - await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); - await expect(service.serviceContract.connect(service.user1).makeDeposit(deposit)).to.be.revertedWithCustomError(service.serviceContract, 'InsufficientAmount') - }); - it("The user deposits to the contract and thus starts the service", async function () { const { token, service } = await loadFixture(deployContract); @@ -136,12 +124,6 @@ import { // advance time and create a new block const block2 = BigInt(await time.increase(30)); - // service contract is subscribed by user1; cannot be subscribed by another user - await expect(service.serviceContract.connect(service.user2).makeDeposit(token.one_token * 1n)).to.be.revertedWithCustomError(service.serviceContract, 'AlreadySubscribed()'); - - // advance time and create a new block - const block3 = BigInt(await time.increase(30)); - // increase the deposit - also increase the allowance deposit = token.one_token * 3n; await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -157,7 +139,7 @@ import { expect(await service.serviceContract.userAddress()).to.equal(service.user1.address); // check the deposit - expect(await service.serviceContract.deposit()).to.equal(5n * token.one_token - (2n * token.one_token / 10n)); + expect(await service.serviceContract.deposit()).to.equal(5n * token.one_token); // check the user's balance, and the provider's expect(await service.serviceContract.connect(service.user1).balanceUser()).to.lessThan(5n * token.one_token); @@ -170,7 +152,7 @@ import { it("The user deposits to the contract and thus starts the service, then stops the service", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -194,7 +176,7 @@ import { it("The user deposits to the contract and thus starts the service, then withdraws which stops the service", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -228,7 +210,7 @@ import { it("The user stops a service, then sends deposits to the service which must fail", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -264,7 +246,7 @@ import { it("The user deposits to the contract and thus starts the service. The provider withdraws some amount.", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -301,7 +283,7 @@ import { it("The user cannot withdraw the provider's balance.", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -317,7 +299,7 @@ import { it("The provider cannot withdraw the user's balance.", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -333,7 +315,7 @@ import { it("The user cannot withdraw more than the balance.", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); @@ -349,7 +331,7 @@ import { it("The provider cannot withdraw more than the balance.", async function () { const { token, service } = await loadFixture(deployContract); - let deposit = token.one_token * 1n + await service.serviceContract.setupFee(); // this is sufficient for one day + let deposit = token.one_token * 1n; // this is sufficient for one day // approve deposit amounts to be spent by the service contract await token.tokenContract.connect(service.user1).approve(service.serviceContract.getAddress(), deposit); From 83aae5635e46b683912b9de09e71c868036b3a0a Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Mon, 18 Nov 2024 23:18:37 +0100 Subject: [PATCH 03/12] corr Signed-off-by: Alexander Diemand --- bca-token-solidity/contracts/BCA_Funding24.sol | 4 ++-- bca-token-solidity/test/BCA_Funding24.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bca-token-solidity/contracts/BCA_Funding24.sol b/bca-token-solidity/contracts/BCA_Funding24.sol index 3dc9543..8deda7a 100644 --- a/bca-token-solidity/contracts/BCA_Funding24.sol +++ b/bca-token-solidity/contracts/BCA_Funding24.sol @@ -49,10 +49,10 @@ contract BCAServiceFunding24 is Iface_Funding24, Ownable { "Insufficient allowance from owner" ); - // // Transfer tokens from owner to this contract + // Transfer tokens from owner to this contract token.safeTransferFrom(owner(), address(this), dailyAmount); - // // Set allowance for the target contract + // Set allowance for the target contract token.approve(targetContract, dailyAmount); try Iface_ServiceInstance(targetContract).makeDeposit(dailyAmount) { diff --git a/bca-token-solidity/test/BCA_Funding24.ts b/bca-token-solidity/test/BCA_Funding24.ts index db91652..58e8abb 100644 --- a/bca-token-solidity/test/BCA_Funding24.ts +++ b/bca-token-solidity/test/BCA_Funding24.ts @@ -71,7 +71,6 @@ import { // Approve funding contract to spend user1's tokens await token.tokenContract.connect(token.user1).approve( funding.fundingContract.getAddress(), - // service.serviceContract.getAddress(), funding.day_funds * 10n ); From b518e7030d8984f7c749b7a72503fd32e3dd4ff6 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Mon, 18 Nov 2024 23:40:20 +0100 Subject: [PATCH 04/12] service manager test Signed-off-by: Alexander Diemand --- bca-token-solidity/test/BCA_Funding24.ts | 2 +- bca-token-solidity/test/BCA_ServiceManager.ts | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 bca-token-solidity/test/BCA_ServiceManager.ts diff --git a/bca-token-solidity/test/BCA_Funding24.ts b/bca-token-solidity/test/BCA_Funding24.ts index 58e8abb..b5520ec 100644 --- a/bca-token-solidity/test/BCA_Funding24.ts +++ b/bca-token-solidity/test/BCA_Funding24.ts @@ -31,7 +31,7 @@ import { // minting some tokens to the users const one_token = 1n * BigInt(10n**precision); - expect(await tokenContract.connect(minter).mint(user1.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user1, 100n*one_token); + expect(await tokenContract.connect(minter).mint(user1.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user1, 10n*one_token); expect(await tokenContract.connect(minter).mint(user2.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user2, 10n*one_token); const startblocktime: bigint = BigInt(await time.increase(30)); diff --git a/bca-token-solidity/test/BCA_ServiceManager.ts b/bca-token-solidity/test/BCA_ServiceManager.ts new file mode 100644 index 0000000..6f4fdd6 --- /dev/null +++ b/bca-token-solidity/test/BCA_ServiceManager.ts @@ -0,0 +1,70 @@ +import { + time, + loadFixture, + } from "@nomicfoundation/hardhat-toolbox/network-helpers"; + import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; + import { expect } from "chai"; + import hre from "hardhat"; + + describe("BCA Service Manager", function () { + // We define a fixture to reuse the same setup in every test. + // We use loadFixture to run this setup once, snapshot that state, + // and reset Hardhat Network to that snapshot in every test. + async function deployContract() { + // Contracts are deployed using the first signer/account by default + const [owner, minter, burner, provider1, provider2, user1, user2] = await hre.ethers.getSigners(); + + const Token = await hre.ethers.getContractFactory("BCAServiceToken"); + const tokenContract = await Token.deploy("Test token", "TOK1", minter, burner); + const precision: bigint = await tokenContract.decimals().then(d => { if (d == 0n) {return 18n;} else {return d}; }); + + const Contract1 = await hre.ethers.getContractFactory("BCAServiceManager"); + const serviceManager1 = await Contract1.deploy(provider1, tokenContract.getAddress()); + + const Contract2 = await hre.ethers.getContractFactory("BCAServiceManager"); + const serviceManager2 = await Contract1.deploy(provider2, tokenContract.getAddress()); + + // minting some tokens to the users + const one_token = 1n * BigInt(10n**precision); + expect(await tokenContract.connect(minter).mint(provider1.address, 10n*one_token)).to.changeTokenBalance(tokenContract, provider1, 10n*one_token); + expect(await tokenContract.connect(minter).mint(provider2.address, 10n*one_token)).to.changeTokenBalance(tokenContract, provider2, 10n*one_token); + expect(await tokenContract.connect(minter).mint(user1.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user1, 10n*one_token); + expect(await tokenContract.connect(minter).mint(user2.address, 10n*one_token)).to.changeTokenBalance(tokenContract, user2, 10n*one_token); + + const startblocktime: bigint = BigInt(await time.increase(30)); + + return { token: { tokenContract, one_token, owner, minter, burner, user1, user2 }, + sm1: { serviceManager1, provider1 }, + sm2: { serviceManager2, provider2 } }; + } + + describe("Deployment", function () { + it("Funding contract: should set the right provider", async function () { + const { sm1 } = await loadFixture(deployContract); + expect(await sm1.serviceManager1.providerAddress()).to.equal( + sm1.provider1.address + ); + }); + + it("Funding contract: should set the right provider", async function () { + const { sm2 } = await loadFixture(deployContract); + expect(await sm2.serviceManager2.providerAddress()).to.equal( + sm2.provider2.address + ); + }); + }); + + describe("Create new services", function () { + it("Should emit ServiceDeployed on new service creation", async function () { + const { sm1, sm2 } = await loadFixture(deployContract); + + await expect(sm1.serviceManager1.connect(sm1.provider1).newService(3, 1n * 10n**18n)) + .to.emit(sm1.serviceManager1, "ServiceDeployed") + .withArgs(anyValue); + await expect(sm2.serviceManager2.connect(sm2.provider2).newService(99, 1n * 10n**18n / 10n)) + .to.emit(sm2.serviceManager2, "ServiceDeployed") + .withArgs(anyValue); + }); + + }); +}); From 59fb11c62d44053bd9035ecb597521a8a56e4f92 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Tue, 19 Nov 2024 00:04:49 +0100 Subject: [PATCH 05/12] follow naming convention Signed-off-by: Alexander Diemand --- bca-token-solidity/contracts/BCA_ERC20_nf.sol | 24 ++++++++--------- .../contracts/BCA_Funding24.sol | 26 +++++++++---------- bca-token-solidity/contracts/BCA_Service.sol | 18 ++++++------- .../contracts/BCA_ServiceInstance.sol | 16 ++++++------ .../contracts/BCA_ServiceManager.sol | 12 ++++----- .../contracts/Iface_Funding24.sol | 2 +- .../contracts/Iface_Service.sol | 2 +- .../contracts/Iface_ServiceInstance.sol | 2 +- .../contracts/Iface_ServiceManager.sol | 2 +- 9 files changed, 52 insertions(+), 52 deletions(-) diff --git a/bca-token-solidity/contracts/BCA_ERC20_nf.sol b/bca-token-solidity/contracts/BCA_ERC20_nf.sol index 85403f6..c49eec8 100644 --- a/bca-token-solidity/contracts/BCA_ERC20_nf.sol +++ b/bca-token-solidity/contracts/BCA_ERC20_nf.sol @@ -31,32 +31,32 @@ contract BCAServiceToken is ERC20, AccessControl { /** * @dev Allows the admin to set the service address. - * @param new_serviceAddress The address to set as the service address. + * @param newServiceAddress The address to set as the service address. */ - function setServiceAddress(address new_serviceAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { + function setServiceAddress(address newServiceAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { _revokeRole(DEFAULT_ADMIN_ROLE, serviceAddress); - serviceAddress = new_serviceAddress; - _grantRole(DEFAULT_ADMIN_ROLE, new_serviceAddress); + serviceAddress = newServiceAddress; + _grantRole(DEFAULT_ADMIN_ROLE, newServiceAddress); } /** * @dev Allows the admin to set the minter address. - * @param new_minterAddress The address to set as the minter address. + * @param newMinterAddress The address to set as the minter address. */ - function setMinterAddress(address new_minterAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { + function setMinterAddress(address newMinterAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { _revokeRole(MINTER_ROLE, minterAddress); - minterAddress = new_minterAddress; - _grantRole(MINTER_ROLE, new_minterAddress); + minterAddress = newMinterAddress; + _grantRole(MINTER_ROLE, newMinterAddress); } /** * @dev Allows the admin to set the burner address. - * @param new_burnerAddress The address to set as the burner address. + * @param newBurnerAddress The address to set as the burner address. */ - function setBurnerAddress(address new_burnerAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { + function setBurnerAddress(address newBurnerAddress) public onlyRole(DEFAULT_ADMIN_ROLE) { _revokeRole(BURNER_ROLE, burnerAddress); - burnerAddress = new_burnerAddress; - _grantRole(BURNER_ROLE, new_burnerAddress); + burnerAddress = newBurnerAddress; + _grantRole(BURNER_ROLE, newBurnerAddress); } /** diff --git a/bca-token-solidity/contracts/BCA_Funding24.sol b/bca-token-solidity/contracts/BCA_Funding24.sol index 8deda7a..4d97e67 100644 --- a/bca-token-solidity/contracts/BCA_Funding24.sol +++ b/bca-token-solidity/contracts/BCA_Funding24.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "./Iface_Funding24.sol"; import "./Iface_ServiceInstance.sol"; -contract BCAServiceFunding24 is Iface_Funding24, Ownable { +contract BCAServiceFunding24 is IFunding24, Ownable { using SafeERC20 for IERC20; address public immutable targetContract; @@ -18,18 +18,18 @@ contract BCAServiceFunding24 is Iface_Funding24, Ownable { event DepositMade(address targetContract, uint256 amount, uint256 timestamp); constructor( - address _initialOwner, - address _targetContract, - address _token, - uint256 _dailyAmount - ) Ownable(_initialOwner) { - require(_targetContract != address(0), "Invalid target contract address"); - require(_token != address(0), "Invalid token address"); - require(_dailyAmount > 0, "Invalid daily amount"); + address initialOwner, + address setTargetContract, + address setToken, + uint256 setDailyAmount + ) Ownable(initialOwner) { + require(setTargetContract != address(0), "Invalid target contract address"); + require(setToken != address(0), "Invalid token address"); + require(setDailyAmount > 0, "Invalid daily amount"); - targetContract = _targetContract; - token = IERC20(_token); - dailyAmount = _dailyAmount; + targetContract = setTargetContract; + token = IERC20(setToken); + dailyAmount = setDailyAmount; lastDepositTime = 0; } @@ -55,7 +55,7 @@ contract BCAServiceFunding24 is Iface_Funding24, Ownable { // Set allowance for the target contract token.approve(targetContract, dailyAmount); - try Iface_ServiceInstance(targetContract).makeDeposit(dailyAmount) { + try IServiceInstance(targetContract).makeDeposit(dailyAmount) { lastDepositTime = block.timestamp; emit DepositMade(targetContract, dailyAmount, block.timestamp); } catch Error(string memory reason) { diff --git a/bca-token-solidity/contracts/BCA_Service.sol b/bca-token-solidity/contracts/BCA_Service.sol index 42693e6..bed91bf 100644 --- a/bca-token-solidity/contracts/BCA_Service.sol +++ b/bca-token-solidity/contracts/BCA_Service.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./Iface_Service.sol"; import "./BCA_ServiceInstance.sol"; -contract BCAService is Iface_Service, ReentrancyGuard { +contract BCAService is IService, ReentrancyGuard { using SafeERC20 for IERC20; // will be set in the constructor @@ -23,20 +23,20 @@ contract BCAService is Iface_Service, ReentrancyGuard { error Exhausted(); - constructor(address _providerAddress, address _tokAddress, - uint16 _maxInstances, uint256 _dayPrice) { - tokToken = IERC20(_tokAddress); - dayPrice = _dayPrice; - providerAddress = _providerAddress; - maxInstances = _maxInstances; + constructor(address setProviderAddress, address tokAddress, + uint16 setMaxInstances, uint256 setDayPrice) { + tokToken = IERC20(tokAddress); + dayPrice = setDayPrice; + providerAddress = setProviderAddress; + maxInstances = setMaxInstances; } - function newInstance(address _userAddress) external nonReentrant returns (address) { + function newInstance(address userAddress) external nonReentrant returns (address) { if (deployedInstances.length >= maxInstances) { revert Exhausted(); } // Create a new SimpleContract - BCAServiceInstance instanceContract = new BCAServiceInstance(providerAddress, address(tokToken), _userAddress, dayPrice); + BCAServiceInstance instanceContract = new BCAServiceInstance(providerAddress, address(tokToken), userAddress, dayPrice); // Store the address deployedInstances.push(address(instanceContract)); diff --git a/bca-token-solidity/contracts/BCA_ServiceInstance.sol b/bca-token-solidity/contracts/BCA_ServiceInstance.sol index 225991c..ae869f2 100644 --- a/bca-token-solidity/contracts/BCA_ServiceInstance.sol +++ b/bca-token-solidity/contracts/BCA_ServiceInstance.sol @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./Iface_ServiceInstance.sol"; -contract BCAServiceInstance is Iface_ServiceInstance, ReentrancyGuard { +contract BCAServiceInstance is IServiceInstance, ReentrancyGuard { using SafeERC20 for IERC20; // will be set in the constructor @@ -32,13 +32,13 @@ contract BCAServiceInstance is Iface_ServiceInstance, ReentrancyGuard { error NotStarted(); error UnAuthorized(); - constructor(address _providerAddress, address _tokAddress, - address _userAddress, - uint256 _dayPrice) { - tokToken = IERC20(_tokAddress); - dayPrice = _dayPrice; - providerAddress = _providerAddress; - userAddress = _userAddress; + constructor(address setProviderAddress, address tokAddress, + address setUserAddress, + uint256 setDayPrice) { + tokToken = IERC20(tokAddress); + dayPrice = setDayPrice; + providerAddress = setProviderAddress; + userAddress = setUserAddress; } function makeDeposit(uint256 amount) external nonReentrant { diff --git a/bca-token-solidity/contracts/BCA_ServiceManager.sol b/bca-token-solidity/contracts/BCA_ServiceManager.sol index 5941740..1f4faec 100644 --- a/bca-token-solidity/contracts/BCA_ServiceManager.sol +++ b/bca-token-solidity/contracts/BCA_ServiceManager.sol @@ -8,7 +8,7 @@ import "./Iface_ServiceManager.sol"; import "./BCA_Service.sol"; // Factory contract that deploys service contracts -contract BCAServiceManager is Iface_ServiceManager, ReentrancyGuard { +contract BCAServiceManager is IServiceManager, ReentrancyGuard { using SafeERC20 for IERC20; IERC20 public immutable tokToken; @@ -18,14 +18,14 @@ contract BCAServiceManager is Iface_ServiceManager, ReentrancyGuard { // Event to notify when a new service is deployed event ServiceDeployed(address contractAddress); - constructor(address _providerAddress, address _tokAddress) { - tokToken = IERC20(_tokAddress); - providerAddress = _providerAddress; + constructor(address setProviderAddress, address tokAddress) { + tokToken = IERC20(tokAddress); + providerAddress = setProviderAddress; } - function newService(uint16 _maxInstances, uint256 _dayPrice) external nonReentrant returns (address) { + function newService(uint16 maxInstances, uint256 dayPrice) external nonReentrant returns (address) { // Create a new SimpleContract - BCAService serviceContract = new BCAService(providerAddress, address(tokToken), _maxInstances, _dayPrice); + BCAService serviceContract = new BCAService(providerAddress, address(tokToken), maxInstances, dayPrice); // Store the address deployedServices.push(address(serviceContract)); diff --git a/bca-token-solidity/contracts/Iface_Funding24.sol b/bca-token-solidity/contracts/Iface_Funding24.sol index 9f2af91..f968989 100644 --- a/bca-token-solidity/contracts/Iface_Funding24.sol +++ b/bca-token-solidity/contracts/Iface_Funding24.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -interface Iface_Funding24 { +interface IFunding24 { function deposit() external; } \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_Service.sol b/bca-token-solidity/contracts/Iface_Service.sol index 668c401..6ac4bc6 100644 --- a/bca-token-solidity/contracts/Iface_Service.sol +++ b/bca-token-solidity/contracts/Iface_Service.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -interface Iface_Service { +interface IService { function newInstance(address _userAddress) external returns (address); } \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_ServiceInstance.sol b/bca-token-solidity/contracts/Iface_ServiceInstance.sol index 652b81f..ec6eecf 100644 --- a/bca-token-solidity/contracts/Iface_ServiceInstance.sol +++ b/bca-token-solidity/contracts/Iface_ServiceInstance.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -interface Iface_ServiceInstance { +interface IServiceInstance { function makeDeposit(uint256 amount) external; function stop() external; function withdrawUser(uint256 amount) external; diff --git a/bca-token-solidity/contracts/Iface_ServiceManager.sol b/bca-token-solidity/contracts/Iface_ServiceManager.sol index 38a6a13..330153f 100644 --- a/bca-token-solidity/contracts/Iface_ServiceManager.sol +++ b/bca-token-solidity/contracts/Iface_ServiceManager.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -interface Iface_ServiceManager { +interface IServiceManager { function newService(uint16 _maxInstances, uint256 _dayPrice) external returns (address); } \ No newline at end of file From 56bec71f5f014b585cb230f6c249eaed9fa42771 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Tue, 19 Nov 2024 00:09:31 +0100 Subject: [PATCH 06/12] updated packages Signed-off-by: Alexander Diemand --- bca-token-solidity/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bca-token-solidity/package.json b/bca-token-solidity/package.json index 23969e3..9a9171c 100644 --- a/bca-token-solidity/package.json +++ b/bca-token-solidity/package.json @@ -5,8 +5,8 @@ "devDependencies": { "@nomicfoundation/hardhat-ignition-ethers": "^0.15.7", "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "hardhat": "^2.22.15", - "mocha": "^10.7.3", + "hardhat": "^2.22.16", + "mocha": "^10.8.2", "solidity-coverage": "^0.8.13" }, "dependencies": { From ddab1edf500634e4b185d5e014d9e7c48e7a5c24 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Tue, 19 Nov 2024 00:25:29 +0100 Subject: [PATCH 07/12] address security warnings Signed-off-by: Alexander Diemand --- bca-token-solidity/contracts/BCA_ERC20_nf.sol | 20 +++++++++---------- .../contracts/BCA_Funding24.sol | 4 +++- .../contracts/BCA_ServiceInstance.sol | 18 +++++++++++++---- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/bca-token-solidity/contracts/BCA_ERC20_nf.sol b/bca-token-solidity/contracts/BCA_ERC20_nf.sol index c49eec8..97ecb99 100644 --- a/bca-token-solidity/contracts/BCA_ERC20_nf.sol +++ b/bca-token-solidity/contracts/BCA_ERC20_nf.sol @@ -13,20 +13,20 @@ contract BCAServiceToken is ERC20, AccessControl { /** * @dev Constructor that sets up roles and gives the deployer the default admin role. - * @param name The name of the token. - * @param symbol The symbol of the token. - * @param minter The address that will be granted the minter role. - * @param burner The address that will be granted the burner role. + * @param setName The name of the token. + * @param setSymbol The symbol of the token. + * @param setMinter The address that will be granted the minter role. + * @param setBurner The address that will be granted the burner role. */ - constructor(string memory name, string memory symbol, address minter, address burner) - ERC20(name, symbol) + constructor(string memory setName, string memory setSymbol, address setMinter, address setBurner) + ERC20(setName, setSymbol) { serviceAddress = msg.sender; - minterAddress = minter; - burnerAddress = burner; + minterAddress = setMinter; + burnerAddress = setBurner; _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); - _grantRole(MINTER_ROLE, minter); - _grantRole(BURNER_ROLE, burner); + _grantRole(MINTER_ROLE, setMinter); + _grantRole(BURNER_ROLE, setBurner); } /** diff --git a/bca-token-solidity/contracts/BCA_Funding24.sol b/bca-token-solidity/contracts/BCA_Funding24.sol index 4d97e67..923781b 100644 --- a/bca-token-solidity/contracts/BCA_Funding24.sol +++ b/bca-token-solidity/contracts/BCA_Funding24.sol @@ -49,6 +49,9 @@ contract BCAServiceFunding24 is IFunding24, Ownable { "Insufficient allowance from owner" ); + // set new state + lastDepositTime = block.timestamp; + // Transfer tokens from owner to this contract token.safeTransferFrom(owner(), address(this), dailyAmount); @@ -56,7 +59,6 @@ contract BCAServiceFunding24 is IFunding24, Ownable { token.approve(targetContract, dailyAmount); try IServiceInstance(targetContract).makeDeposit(dailyAmount) { - lastDepositTime = block.timestamp; emit DepositMade(targetContract, dailyAmount, block.timestamp); } catch Error(string memory reason) { revert(reason); diff --git a/bca-token-solidity/contracts/BCA_ServiceInstance.sol b/bca-token-solidity/contracts/BCA_ServiceInstance.sol index ae869f2..c6f394d 100644 --- a/bca-token-solidity/contracts/BCA_ServiceInstance.sol +++ b/bca-token-solidity/contracts/BCA_ServiceInstance.sol @@ -128,9 +128,14 @@ contract BCAServiceInstance is IServiceInstance, ReentrancyGuard { revert InsufficientBalance(balance); } - tokToken.transfer(msg.sender, amount); + // set new state deposit -= amount; - emit Withdrawn(msg.sender, amount); + + if (tokToken.transfer(msg.sender, amount)) { + emit Withdrawn(msg.sender, amount); + } else { + revert("transfer failed"); + } // stop service? if (balance - amount <= dayPrice / 24 / 3600) { @@ -153,8 +158,13 @@ contract BCAServiceInstance is IServiceInstance, ReentrancyGuard { _stop(msg.sender); } - tokToken.transfer(msg.sender, amount); + // set new state retracted += amount; - emit Retracted(msg.sender, amount); + + if (tokToken.transfer(msg.sender, amount)) { + emit Retracted(msg.sender, amount); + } else { + revert("transfer failed"); + } } } \ No newline at end of file From 216062a25c9d4f1b8f389bd8714932cf39c7ea8b Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Tue, 19 Nov 2024 00:35:37 +0100 Subject: [PATCH 08/12] check constructor arguments Signed-off-by: Alexander Diemand --- bca-token-solidity/contracts/BCA_ERC20_nf.sol | 3 +++ bca-token-solidity/contracts/BCA_Service.sol | 3 +++ bca-token-solidity/contracts/BCA_ServiceInstance.sol | 4 ++++ bca-token-solidity/contracts/BCA_ServiceManager.sol | 3 +++ 4 files changed, 13 insertions(+) diff --git a/bca-token-solidity/contracts/BCA_ERC20_nf.sol b/bca-token-solidity/contracts/BCA_ERC20_nf.sol index 97ecb99..d892323 100644 --- a/bca-token-solidity/contracts/BCA_ERC20_nf.sol +++ b/bca-token-solidity/contracts/BCA_ERC20_nf.sol @@ -21,6 +21,9 @@ contract BCAServiceToken is ERC20, AccessControl { constructor(string memory setName, string memory setSymbol, address setMinter, address setBurner) ERC20(setName, setSymbol) { + require(setMinter != address(0), "Invalid minter address"); + require(setBurner != address(0), "Invalid burner address"); + serviceAddress = msg.sender; minterAddress = setMinter; burnerAddress = setBurner; diff --git a/bca-token-solidity/contracts/BCA_Service.sol b/bca-token-solidity/contracts/BCA_Service.sol index bed91bf..5d29fd6 100644 --- a/bca-token-solidity/contracts/BCA_Service.sol +++ b/bca-token-solidity/contracts/BCA_Service.sol @@ -25,6 +25,9 @@ contract BCAService is IService, ReentrancyGuard { constructor(address setProviderAddress, address tokAddress, uint16 setMaxInstances, uint256 setDayPrice) { + require(setProviderAddress != address(0), "Invalid provider address"); + require(tokAddress != address(0), "Invalid token address"); + tokToken = IERC20(tokAddress); dayPrice = setDayPrice; providerAddress = setProviderAddress; diff --git a/bca-token-solidity/contracts/BCA_ServiceInstance.sol b/bca-token-solidity/contracts/BCA_ServiceInstance.sol index c6f394d..92985e8 100644 --- a/bca-token-solidity/contracts/BCA_ServiceInstance.sol +++ b/bca-token-solidity/contracts/BCA_ServiceInstance.sol @@ -35,6 +35,10 @@ contract BCAServiceInstance is IServiceInstance, ReentrancyGuard { constructor(address setProviderAddress, address tokAddress, address setUserAddress, uint256 setDayPrice) { + require(setProviderAddress != address(0), "Invalid provider address"); + require(tokAddress != address(0), "Invalid token address"); + require(setUserAddress != address(0), "Invalid user address"); + tokToken = IERC20(tokAddress); dayPrice = setDayPrice; providerAddress = setProviderAddress; diff --git a/bca-token-solidity/contracts/BCA_ServiceManager.sol b/bca-token-solidity/contracts/BCA_ServiceManager.sol index 1f4faec..79821b7 100644 --- a/bca-token-solidity/contracts/BCA_ServiceManager.sol +++ b/bca-token-solidity/contracts/BCA_ServiceManager.sol @@ -19,6 +19,9 @@ contract BCAServiceManager is IServiceManager, ReentrancyGuard { event ServiceDeployed(address contractAddress); constructor(address setProviderAddress, address tokAddress) { + require(setProviderAddress != address(0), "Invalid provider address"); + require(tokAddress != address(0), "Invalid token address"); + tokToken = IERC20(tokAddress); providerAddress = setProviderAddress; } From cb1a526703411219ca370ba8b20b32d102320e87 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Tue, 19 Nov 2024 16:46:54 +0100 Subject: [PATCH 09/12] new hierachy: ServiceManager - ServiceController - Service - Instance Signed-off-by: Alexander Diemand --- bca-token-solidity/README.md | 2 +- .../contracts/BCA_ServiceController.sol | 45 +++++++++++++++++ .../contracts/BCA_ServiceManager.sol | 47 +++++++++++------- .../contracts/Iface_ServiceController.sol | 6 +++ .../contracts/Iface_ServiceManager.sol | 2 +- .../ignition/modules/BCA_ServiceManager.ts | 2 +- bca-token-solidity/test/BCA_ServiceManager.ts | 48 +++++++++++-------- 7 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 bca-token-solidity/contracts/BCA_ServiceController.sol create mode 100644 bca-token-solidity/contracts/Iface_ServiceController.sol diff --git a/bca-token-solidity/README.md b/bca-token-solidity/README.md index e44ff4b..0caac7f 100644 --- a/bca-token-solidity/README.md +++ b/bca-token-solidity/README.md @@ -23,7 +23,7 @@ npx hardhat node in the other deploy the contract ```sh npx hardhat ignition deploy ignition/modules/BCA_Token.ts --network localhost -npx hardhat ignition deploy ignition/modules/BCA_Service.ts --network localhost +npx hardhat ignition deploy ignition/modules/BCA_ServiceManager.ts --network localhost ``` #### Connect metamask to local node diff --git a/bca-token-solidity/contracts/BCA_ServiceController.sol b/bca-token-solidity/contracts/BCA_ServiceController.sol new file mode 100644 index 0000000..0099fe7 --- /dev/null +++ b/bca-token-solidity/contracts/BCA_ServiceController.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./Iface_ServiceController.sol"; +import "./BCA_Service.sol"; + +// Factory contract that deploys service contracts +contract BCAServiceController is IServiceController, ReentrancyGuard { + using SafeERC20 for IERC20; + + IERC20 public immutable tokToken; + address public immutable providerAddress; + address[] public deployedServices; + + // Event to notify when a new service is deployed + event ServiceDeployed(address contractAddress); + + constructor(address setProviderAddress, address tokAddress) { + require(setProviderAddress != address(0), "Invalid provider address"); + require(tokAddress != address(0), "Invalid token address"); + + tokToken = IERC20(tokAddress); + providerAddress = setProviderAddress; + } + + function newService(uint16 maxInstances, uint256 dayPrice) external nonReentrant returns (address) { + // Create a new SimpleContract + BCAService serviceContract = new BCAService(providerAddress, address(tokToken), maxInstances, dayPrice); + + // Store the address + deployedServices.push(address(serviceContract)); + + // Emit event + emit ServiceDeployed(address(serviceContract)); + + return address(serviceContract); + } + + function countServiceContracts() public view returns (uint) { + return deployedServices.length; + } +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/BCA_ServiceManager.sol b/bca-token-solidity/contracts/BCA_ServiceManager.sol index 79821b7..5f4fde3 100644 --- a/bca-token-solidity/contracts/BCA_ServiceManager.sol +++ b/bca-token-solidity/contracts/BCA_ServiceManager.sol @@ -5,41 +5,56 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./Iface_ServiceManager.sol"; -import "./BCA_Service.sol"; +import "./BCA_ServiceController.sol"; // Factory contract that deploys service contracts contract BCAServiceManager is IServiceManager, ReentrancyGuard { using SafeERC20 for IERC20; IERC20 public immutable tokToken; - address public immutable providerAddress; - address[] public deployedServices; + + struct ControllerStruct { + address addrContract; + bool isDeployed; + } + mapping (address => ControllerStruct) public deployedControllers; + address[] public addressControllers; // Event to notify when a new service is deployed - event ServiceDeployed(address contractAddress); + event ControllerDeployed(address contractAddress); - constructor(address setProviderAddress, address tokAddress) { - require(setProviderAddress != address(0), "Invalid provider address"); + constructor(address tokAddress) { require(tokAddress != address(0), "Invalid token address"); tokToken = IERC20(tokAddress); - providerAddress = setProviderAddress; } - function newService(uint16 maxInstances, uint256 dayPrice) external nonReentrant returns (address) { + function getControllerAddress(address providerAddress) public view returns(address addrController) { + require (deployedControllers[providerAddress].isDeployed == true, "no controller for this provider"); + return deployedControllers[providerAddress].addrContract; + } + + function isDeployed(address providerAddress) public view returns(bool isdeployed) { + if (addressControllers.length == 0) return false; + return (deployedControllers[providerAddress].isDeployed); + } + + function newController(address providerAddress) external nonReentrant { + require(! isDeployed(providerAddress), "already deployed controller for this provider"); + // Create a new SimpleContract - BCAService serviceContract = new BCAService(providerAddress, address(tokToken), maxInstances, dayPrice); + BCAServiceController controllerContract = new BCAServiceController(providerAddress, address(tokToken)); // Store the address - deployedServices.push(address(serviceContract)); + addressControllers.push(address(controllerContract)); + deployedControllers[providerAddress].addrContract = address(controllerContract); + deployedControllers[providerAddress].isDeployed = true; // Emit event - emit ServiceDeployed(address(serviceContract)); - - return address(serviceContract); + emit ControllerDeployed(address(controllerContract)); } - - function countServiceContracts() public view returns (uint) { - return deployedServices.length; + + function countServiceControllers() public view returns (uint) { + return addressControllers.length; } } \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_ServiceController.sol b/bca-token-solidity/contracts/Iface_ServiceController.sol new file mode 100644 index 0000000..863e016 --- /dev/null +++ b/bca-token-solidity/contracts/Iface_ServiceController.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +interface IServiceController { + function newService(uint16 _maxInstances, uint256 _dayPrice) external returns (address); +} \ No newline at end of file diff --git a/bca-token-solidity/contracts/Iface_ServiceManager.sol b/bca-token-solidity/contracts/Iface_ServiceManager.sol index 330153f..0cc1b62 100644 --- a/bca-token-solidity/contracts/Iface_ServiceManager.sol +++ b/bca-token-solidity/contracts/Iface_ServiceManager.sol @@ -2,5 +2,5 @@ pragma solidity ^0.8.24; interface IServiceManager { - function newService(uint16 _maxInstances, uint256 _dayPrice) external returns (address); + function newController(address providerAddress) external; } \ No newline at end of file diff --git a/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts b/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts index 1f4e32d..57aadc3 100644 --- a/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts +++ b/bca-token-solidity/ignition/modules/BCA_ServiceManager.ts @@ -12,7 +12,7 @@ let provider = "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" const BCAServiceManagerModule = buildModule("BCA_ServiceManager", (m) => { - const bcasrvmgr = m.contract("BCAServiceManager", [provider, deployed_address["BCA_Token#BCAServiceToken"]]); + const bcasrvmgr = m.contract("BCAServiceManager", [deployed_address["BCA_Token#BCAServiceToken"]]); return { bcasrvmgr }; }); diff --git a/bca-token-solidity/test/BCA_ServiceManager.ts b/bca-token-solidity/test/BCA_ServiceManager.ts index 6f4fdd6..64b644c 100644 --- a/bca-token-solidity/test/BCA_ServiceManager.ts +++ b/bca-token-solidity/test/BCA_ServiceManager.ts @@ -18,11 +18,13 @@ import { const tokenContract = await Token.deploy("Test token", "TOK1", minter, burner); const precision: bigint = await tokenContract.decimals().then(d => { if (d == 0n) {return 18n;} else {return d}; }); - const Contract1 = await hre.ethers.getContractFactory("BCAServiceManager"); - const serviceManager1 = await Contract1.deploy(provider1, tokenContract.getAddress()); + const Manager = await hre.ethers.getContractFactory("BCAServiceManager"); + const serviceManager = await Manager.deploy(tokenContract.getAddress()); - const Contract2 = await hre.ethers.getContractFactory("BCAServiceManager"); - const serviceManager2 = await Contract1.deploy(provider2, tokenContract.getAddress()); + const addrController1 = await (await serviceManager.connect(owner).newController(provider1)).wait().then(_ => serviceManager.connect(owner).getControllerAddress(provider1)); + const serviceController1 = await hre.ethers.getContractAt("BCAServiceController", addrController1) + const addrController2 = await (await serviceManager.connect(owner).newController(provider2)).wait().then(_ => serviceManager.connect(owner).getControllerAddress(provider2)); + const serviceController2 = await hre.ethers.getContractAt("BCAServiceController", addrController2) // minting some tokens to the users const one_token = 1n * BigInt(10n**precision); @@ -34,35 +36,43 @@ import { const startblocktime: bigint = BigInt(await time.increase(30)); return { token: { tokenContract, one_token, owner, minter, burner, user1, user2 }, - sm1: { serviceManager1, provider1 }, - sm2: { serviceManager2, provider2 } }; + sm: { serviceManager, provider1, provider2 }, + sc1: { serviceController1, provider1 }, + sc2: { serviceController2, provider2 } }; } describe("Deployment", function () { - it("Funding contract: should set the right provider", async function () { - const { sm1 } = await loadFixture(deployContract); - expect(await sm1.serviceManager1.providerAddress()).to.equal( - sm1.provider1.address + it("Should have already deployed controllers", async function () { + const { sm } = await loadFixture(deployContract); + expect(await sm.serviceManager.countServiceControllers()).to.equal( + 2 ); }); - it("Funding contract: should set the right provider", async function () { - const { sm2 } = await loadFixture(deployContract); - expect(await sm2.serviceManager2.providerAddress()).to.equal( - sm2.provider2.address + it("Should set the right provider", async function () { + const { sc1 } = await loadFixture(deployContract); + expect(await sc1.serviceController1.providerAddress()).to.equal( + sc1.provider1.address + ); + }); + + it("Should set the right provider", async function () { + const { sc2 } = await loadFixture(deployContract); + expect(await sc2.serviceController2.providerAddress()).to.equal( + sc2.provider2.address ); }); }); describe("Create new services", function () { it("Should emit ServiceDeployed on new service creation", async function () { - const { sm1, sm2 } = await loadFixture(deployContract); + const { sc1, sc2 } = await loadFixture(deployContract); - await expect(sm1.serviceManager1.connect(sm1.provider1).newService(3, 1n * 10n**18n)) - .to.emit(sm1.serviceManager1, "ServiceDeployed") + await expect(sc1.serviceController1.connect(sc1.provider1).newService(3, 1n * 10n**18n)) + .to.emit(sc1.serviceController1, "ServiceDeployed") .withArgs(anyValue); - await expect(sm2.serviceManager2.connect(sm2.provider2).newService(99, 1n * 10n**18n / 10n)) - .to.emit(sm2.serviceManager2, "ServiceDeployed") + await expect(sc2.serviceController2.connect(sc2.provider2).newService(99, 1n * 10n**18n / 10n)) + .to.emit(sc2.serviceController2, "ServiceDeployed") .withArgs(anyValue); }); From 001bd7988cf962f07ceb583e9111d84d50de1110 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Wed, 20 Nov 2024 23:25:43 +0100 Subject: [PATCH 10/12] wip - user interface to manage contracts Signed-off-by: Alexander Diemand --- bca-token-market/.gitignore | 1 + bca-token-market/src/lib/bca_service-abi.json | 376 ---------- bca-token-market/src/lib/bca_token-abi.json | 710 ------------------ bca-token-market/src/lib/contracts.ts | 13 +- .../src/routes/service/+page.server.ts | 12 + .../src/routes/service/[addr]/+page.server.ts | 5 + .../src/routes/service/[addr]/+page.svelte | 160 ++++ .../routes/servicecontroller/+page.server.ts | 12 + .../servicecontroller/[addr]/+page.server.ts | 5 + .../servicecontroller/[addr]/+page.svelte | 169 +++++ .../routes/serviceinstance/+page.server.ts | 12 + .../serviceinstance/[addr]/+page.server.ts | 5 + .../serviceinstance/[addr]/+page.svelte | 276 +++++++ .../src/routes/servicemanager/+page.server.ts | 12 + .../src/routes/servicemanager/+page.svelte | 17 + .../servicemanager/[addr]/+page.server.ts | 5 + .../routes/servicemanager/[addr]/+page.svelte | 158 ++++ bca-token-market/update-abi.sh | 10 + .../contracts/BCA_ServiceManager.sol | 10 +- 19 files changed, 875 insertions(+), 1093 deletions(-) delete mode 100644 bca-token-market/src/lib/bca_service-abi.json delete mode 100644 bca-token-market/src/lib/bca_token-abi.json create mode 100644 bca-token-market/src/routes/service/+page.server.ts create mode 100644 bca-token-market/src/routes/service/[addr]/+page.server.ts create mode 100644 bca-token-market/src/routes/service/[addr]/+page.svelte create mode 100644 bca-token-market/src/routes/servicecontroller/+page.server.ts create mode 100644 bca-token-market/src/routes/servicecontroller/[addr]/+page.server.ts create mode 100644 bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte create mode 100644 bca-token-market/src/routes/serviceinstance/+page.server.ts create mode 100644 bca-token-market/src/routes/serviceinstance/[addr]/+page.server.ts create mode 100644 bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte create mode 100644 bca-token-market/src/routes/servicemanager/+page.server.ts create mode 100644 bca-token-market/src/routes/servicemanager/+page.svelte create mode 100644 bca-token-market/src/routes/servicemanager/[addr]/+page.server.ts create mode 100644 bca-token-market/src/routes/servicemanager/[addr]/+page.svelte create mode 100755 bca-token-market/update-abi.sh diff --git a/bca-token-market/.gitignore b/bca-token-market/.gitignore index b81ebcb..a5bfebc 100644 --- a/bca-token-market/.gitignore +++ b/bca-token-market/.gitignore @@ -22,3 +22,4 @@ node_modules package-lock.json prisma/migrations +src/lib/bca_*-abi.json diff --git a/bca-token-market/src/lib/bca_service-abi.json b/bca-token-market/src/lib/bca_service-abi.json deleted file mode 100644 index d3f23ad..0000000 --- a/bca-token-market/src/lib/bca_service-abi.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "BCAServiceContract", - "sourceName": "contracts/BCA_Service.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_providerAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_tokAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_dayPrice", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddressInsufficientBalance", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadyStopped", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadySubscribed", - "type": "error" - }, - { - "inputs": [], - "name": "FailedInnerCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "minimumAmount", - "type": "uint256" - } - ], - "name": "InsufficientAmount", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "availableBalance", - "type": "uint256" - } - ], - "name": "InsufficientBalance", - "type": "error" - }, - { - "inputs": [], - "name": "NotStarted", - "type": "error" - }, - { - "inputs": [], - "name": "ReentrancyGuardReentrantCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" - }, - { - "inputs": [], - "name": "UnAuthorized", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "DepositMade", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Retracted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "ticks", - "type": "uint256" - } - ], - "name": "ServiceStopped", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Withdrawn", - "type": "event" - }, - { - "inputs": [], - "name": "balanceProvider", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "balanceUser", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "dayPrice", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "endTime", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "makeDeposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "providerAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "retracted", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "setupFee", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startTime", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "stop", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "tokToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "userAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawProvider", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawUser", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60e060405234801561001057600080fd5b5060405161100438038061100483398101604081905261002f9161006a565b60016000556001600160a01b0391821660805260a0521660c0526100a6565b80516001600160a01b038116811461006557600080fd5b919050565b60008060006060848603121561007f57600080fd5b6100888461004e565b92506100966020850161004e565b9150604084015190509250925092565b60805160a05160c051610edb6101296000396000818161014001528181610493015281816105b30152610af801526000818161017f015281816102ae015281816102f30152818161052b0152818161062b0152818161080a01526109bd01526000818161021101528181610395015281816106b601526109040152610edb6000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80639cf81fb311610097578063d39f075b11610066578063d39f075b146101dd578063dab70e3c146101e6578063e4128fb3146101f9578063ff1f7f0c1461020c57600080fd5b80639cf81fb3146101aa578063a40b56b9146101bd578063d0e30db0146101cc578063d25120ce146101d557600080fd5b80633197cbb6116100d35780633197cbb614610132578063706e52571461013b578063742c9c8f1461017a57806378e97925146101a157600080fd5b8063059a500c146100fa57806307da68f51461010f578063229edc9e14610117575b600080fd5b61010d610108366004610dc6565b610233565b005b61010d61047b565b61011f610486565b6040519081526020015b60405180910390f35b61011f60045481565b6101627f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610129565b61011f7f000000000000000000000000000000000000000000000000000000000000000081565b61011f60035481565b61010d6101b8366004610dc6565b6105a0565b61011f6702c68af0bb14000081565b61011f60015481565b61011f610770565b61011f60025481565b61010d6101f4366004610dc6565b61086e565b600554610162906001600160a01b031681565b6101627f000000000000000000000000000000000000000000000000000000000000000081565b61023b610a0e565b6005546001600160a01b0316158061025d57506005546001600160a01b031633145b610293576040517f5fd8a13200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546001600160a01b03161580156102e757506102d260187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6102e4906702c68af0bb140000610e17565b81105b156103675761031760187f0000000000000000000000000000000000000000000000000000000000000000610df5565b610329906702c68af0bb140000610e17565b6040517f77b8dde300000000000000000000000000000000000000000000000000000000815260040161035e91815260200190565b60405180910390fd5b60045415610388576040516307ccaad360e21b815260040160405180910390fd5b6103bd6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016333084610a51565b80600160008282546103cf9190610e17565b90915550506005546001600160a01b031661043557600580547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317905542600355600180546702c68af0bb140000919060009061042f908490610e2a565b90915550505b60408051338152602081018390527fd15c9547ea5c06670c0010ce19bc32d54682a4b3801ece7f3ab0c3f17106b4bb910160405180910390a16104786001600055565b50565b61048433610adf565b565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104d15760405163be24598360e01b815260040160405180910390fd5b6003546000036104f457604051636f312cbd60e01b815260040160405180910390fd5b6000600454600003610506574261050a565b6004545b905060006003548261051c9190610e2a565b9050600080610e1060186105507f000000000000000000000000000000000000000000000000000000000000000086610e3d565b61055a9190610df5565b6105649190610df5565b90506002546001546105769190610e2a565b8111156105945760025460015461058d9190610e2a565b9150610598565b8091505b509392505050565b6105a8610a0e565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105f15760405163be24598360e01b815260040160405180910390fd5b60006105fb610486565b90508181101561062157604051639266535160e01b81526004810182905260240161035e565b610e1061064f60187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6106599190610df5565b8260025460015461066a9190610e2a565b6106749190610e2a565b10156106835761068333610adf565b81600260008282546106959190610e17565b909155505060405163a9059cbb60e01b8152336004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072b9190610e54565b5060408051338152602081018490527fbeab809f862bcac7e41d0cc40843a6c895216194f5e2ec16d9b8e41de89d2794910160405180910390a1506104786001600055565b6005546000906001600160a01b0316158061078b5750600354155b156107a957604051636f312cbd60e01b815260040160405180910390fd5b6005546001600160a01b031633146107d45760405163be24598360e01b815260040160405180910390fd5b60006004546000036107e657426107ea565b6004545b90506000600354826107fc9190610e2a565b90506000610e10601861082f7f000000000000000000000000000000000000000000000000000000000000000085610e3d565b6108399190610df5565b6108439190610df5565b90506001548110610858576000935050505090565b806001546108669190610e2a565b935050505090565b610876610a0e565b6005546001600160a01b031633146108a15760405163be24598360e01b815260040160405180910390fd5b60006108ab610770565b9050818110156108d157604051639266535160e01b81526004810182905260240161035e565b81600160008282546108e39190610e2a565b909155505060405163a9059cbb60e01b8152336004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109799190610e54565b5060408051338152602081018490527f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5910160405180910390a1610e106109e160187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6109eb9190610df5565b6109f58383610e2a565b11610a0357610a0333610adf565b506104786001600055565b600260005403610a4a576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600055565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd00000000000000000000000000000000000000000000000000000000179052610ad9908590610bbf565b50505050565b6005546001600160a01b0382811691161480610b2c57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b0316145b610b495760405163be24598360e01b815260040160405180910390fd5b60045415610b6a576040516307ccaad360e21b815260040160405180910390fd5b426004819055600354600091610b809190610e2a565b90507f65cd0e12bdc2afa1e1eba1853832b1c67e6318096add2681d6e020a7ed1ce8a181604051610bb391815260200190565b60405180910390a15050565b6000610bd46001600160a01b03841683610c40565b90508051600014158015610bf9575080806020019051810190610bf79190610e54565b155b15610c3b576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038416600482015260240161035e565b505050565b6060610c4e83836000610c57565b90505b92915050565b606081471015610c95576040517fcd78605900000000000000000000000000000000000000000000000000000000815230600482015260240161035e565b600080856001600160a01b03168486604051610cb19190610e76565b60006040518083038185875af1925050503d8060008114610cee576040519150601f19603f3d011682016040523d82523d6000602084013e610cf3565b606091505b5091509150610d03868383610d0f565b925050505b9392505050565b606082610d2457610d1f82610d84565b610d08565b8151158015610d3b57506001600160a01b0384163b155b15610d7d576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161035e565b5080610d08565b805115610d945780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060208284031215610dd857600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b600082610e1257634e487b7160e01b600052601260045260246000fd5b500490565b80820180821115610c5157610c51610ddf565b81810381811115610c5157610c51610ddf565b8082028115828204841417610c5157610c51610ddf565b600060208284031215610e6657600080fd5b81518015158114610d0857600080fd5b6000825160005b81811015610e975760208186018101518583015201610e7d565b50600092019182525091905056fea26469706673582212205b832f12834ce20b67b8df19d4bfafd4de31be0ae9e7949e55ccfe242216dbf664736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100f55760003560e01c80639cf81fb311610097578063d39f075b11610066578063d39f075b146101dd578063dab70e3c146101e6578063e4128fb3146101f9578063ff1f7f0c1461020c57600080fd5b80639cf81fb3146101aa578063a40b56b9146101bd578063d0e30db0146101cc578063d25120ce146101d557600080fd5b80633197cbb6116100d35780633197cbb614610132578063706e52571461013b578063742c9c8f1461017a57806378e97925146101a157600080fd5b8063059a500c146100fa57806307da68f51461010f578063229edc9e14610117575b600080fd5b61010d610108366004610dc6565b610233565b005b61010d61047b565b61011f610486565b6040519081526020015b60405180910390f35b61011f60045481565b6101627f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610129565b61011f7f000000000000000000000000000000000000000000000000000000000000000081565b61011f60035481565b61010d6101b8366004610dc6565b6105a0565b61011f6702c68af0bb14000081565b61011f60015481565b61011f610770565b61011f60025481565b61010d6101f4366004610dc6565b61086e565b600554610162906001600160a01b031681565b6101627f000000000000000000000000000000000000000000000000000000000000000081565b61023b610a0e565b6005546001600160a01b0316158061025d57506005546001600160a01b031633145b610293576040517f5fd8a13200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546001600160a01b03161580156102e757506102d260187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6102e4906702c68af0bb140000610e17565b81105b156103675761031760187f0000000000000000000000000000000000000000000000000000000000000000610df5565b610329906702c68af0bb140000610e17565b6040517f77b8dde300000000000000000000000000000000000000000000000000000000815260040161035e91815260200190565b60405180910390fd5b60045415610388576040516307ccaad360e21b815260040160405180910390fd5b6103bd6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016333084610a51565b80600160008282546103cf9190610e17565b90915550506005546001600160a01b031661043557600580547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317905542600355600180546702c68af0bb140000919060009061042f908490610e2a565b90915550505b60408051338152602081018390527fd15c9547ea5c06670c0010ce19bc32d54682a4b3801ece7f3ab0c3f17106b4bb910160405180910390a16104786001600055565b50565b61048433610adf565b565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104d15760405163be24598360e01b815260040160405180910390fd5b6003546000036104f457604051636f312cbd60e01b815260040160405180910390fd5b6000600454600003610506574261050a565b6004545b905060006003548261051c9190610e2a565b9050600080610e1060186105507f000000000000000000000000000000000000000000000000000000000000000086610e3d565b61055a9190610df5565b6105649190610df5565b90506002546001546105769190610e2a565b8111156105945760025460015461058d9190610e2a565b9150610598565b8091505b509392505050565b6105a8610a0e565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105f15760405163be24598360e01b815260040160405180910390fd5b60006105fb610486565b90508181101561062157604051639266535160e01b81526004810182905260240161035e565b610e1061064f60187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6106599190610df5565b8260025460015461066a9190610e2a565b6106749190610e2a565b10156106835761068333610adf565b81600260008282546106959190610e17565b909155505060405163a9059cbb60e01b8152336004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610707573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072b9190610e54565b5060408051338152602081018490527fbeab809f862bcac7e41d0cc40843a6c895216194f5e2ec16d9b8e41de89d2794910160405180910390a1506104786001600055565b6005546000906001600160a01b0316158061078b5750600354155b156107a957604051636f312cbd60e01b815260040160405180910390fd5b6005546001600160a01b031633146107d45760405163be24598360e01b815260040160405180910390fd5b60006004546000036107e657426107ea565b6004545b90506000600354826107fc9190610e2a565b90506000610e10601861082f7f000000000000000000000000000000000000000000000000000000000000000085610e3d565b6108399190610df5565b6108439190610df5565b90506001548110610858576000935050505090565b806001546108669190610e2a565b935050505090565b610876610a0e565b6005546001600160a01b031633146108a15760405163be24598360e01b815260040160405180910390fd5b60006108ab610770565b9050818110156108d157604051639266535160e01b81526004810182905260240161035e565b81600160008282546108e39190610e2a565b909155505060405163a9059cbb60e01b8152336004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109799190610e54565b5060408051338152602081018490527f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5910160405180910390a1610e106109e160187f0000000000000000000000000000000000000000000000000000000000000000610df5565b6109eb9190610df5565b6109f58383610e2a565b11610a0357610a0333610adf565b506104786001600055565b600260005403610a4a576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600055565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd00000000000000000000000000000000000000000000000000000000179052610ad9908590610bbf565b50505050565b6005546001600160a01b0382811691161480610b2c57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b0316145b610b495760405163be24598360e01b815260040160405180910390fd5b60045415610b6a576040516307ccaad360e21b815260040160405180910390fd5b426004819055600354600091610b809190610e2a565b90507f65cd0e12bdc2afa1e1eba1853832b1c67e6318096add2681d6e020a7ed1ce8a181604051610bb391815260200190565b60405180910390a15050565b6000610bd46001600160a01b03841683610c40565b90508051600014158015610bf9575080806020019051810190610bf79190610e54565b155b15610c3b576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038416600482015260240161035e565b505050565b6060610c4e83836000610c57565b90505b92915050565b606081471015610c95576040517fcd78605900000000000000000000000000000000000000000000000000000000815230600482015260240161035e565b600080856001600160a01b03168486604051610cb19190610e76565b60006040518083038185875af1925050503d8060008114610cee576040519150601f19603f3d011682016040523d82523d6000602084013e610cf3565b606091505b5091509150610d03868383610d0f565b925050505b9392505050565b606082610d2457610d1f82610d84565b610d08565b8151158015610d3b57506001600160a01b0384163b155b15610d7d576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240161035e565b5080610d08565b805115610d945780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060208284031215610dd857600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b600082610e1257634e487b7160e01b600052601260045260246000fd5b500490565b80820180821115610c5157610c51610ddf565b81810381811115610c5157610c51610ddf565b8082028115828204841417610c5157610c51610ddf565b600060208284031215610e6657600080fd5b81518015158114610d0857600080fd5b6000825160005b81811015610e975760208186018101518583015201610e7d565b50600092019182525091905056fea26469706673582212205b832f12834ce20b67b8df19d4bfafd4de31be0ae9e7949e55ccfe242216dbf664736f6c63430008180033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/bca-token-market/src/lib/bca_token-abi.json b/bca-token-market/src/lib/bca_token-abi.json deleted file mode 100644 index 0ad48c5..0000000 --- a/bca-token-market/src/lib/bca_token-abi.json +++ /dev/null @@ -1,710 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "BCAServiceToken", - "sourceName": "contracts/BCA_ERC20_nf.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "internalType": "address", - "name": "burner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "AccessControlBadConfirmation", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "neededRole", - "type": "bytes32" - } - ], - "name": "AccessControlUnauthorizedAccount", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "allowance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientAllowance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientBalance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "approver", - "type": "address" - } - ], - "name": "ERC20InvalidApprover", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "ERC20InvalidReceiver", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "ERC20InvalidSender", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "ERC20InvalidSpender", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "previousAdminRole", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "newAdminRole", - "type": "bytes32" - } - ], - "name": "RoleAdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "BURNER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DEFAULT_ADMIN_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MINTER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "burnerAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } - ], - "name": "getRoleAdmin", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "hasRole", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "minterAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "callerConfirmation", - "type": "address" - } - ], - "name": "renounceRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "serviceAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "new_burnerAddress", - "type": "address" - } - ], - "name": "setBurnerAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "new_minterAddress", - "type": "address" - } - ], - "name": "setMinterAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "new_serviceAddress", - "type": "address" - } - ], - "name": "setServiceAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60806040523480156200001157600080fd5b50604051620015353803806200153583398101604081905262000034916200029c565b83836003620000448382620003bc565b506004620000538282620003bc565b505060068054336001600160a01b031991821681179092556007805482166001600160a01b0387811691909117909155600880549092169085161790556200009f915060009062000104565b50620000cc7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68362000104565b50620000f97f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a8488262000104565b505050505062000488565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff16620001ad5760008381526005602090815260408083206001600160a01b03861684529091529020805460ff19166001179055620001643390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4506001620001b1565b5060005b92915050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001df57600080fd5b81516001600160401b0380821115620001fc57620001fc620001b7565b604051601f8301601f19908116603f01168101908282118183101715620002275762000227620001b7565b81604052838152602092508660208588010111156200024557600080fd5b600091505b838210156200026957858201830151818301840152908201906200024a565b6000602085830101528094505050505092915050565b80516001600160a01b03811681146200029757600080fd5b919050565b60008060008060808587031215620002b357600080fd5b84516001600160401b0380821115620002cb57600080fd5b620002d988838901620001cd565b95506020870151915080821115620002f057600080fd5b50620002ff87828801620001cd565b93505062000310604086016200027f565b915062000320606086016200027f565b905092959194509250565b600181811c908216806200034057607f821691505b6020821081036200036157634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620003b7576000816000526020600020601f850160051c81016020861015620003925750805b601f850160051c820191505b81811015620003b3578281556001016200039e565b5050505b505050565b81516001600160401b03811115620003d857620003d8620001b7565b620003f081620003e984546200032b565b8462000367565b602080601f8311600181146200042857600084156200040f5750858301515b600019600386901b1c1916600185901b178555620003b3565b600085815260208120601f198616915b82811015620004595788860151825594840194600190910190840162000438565b5085821015620004785787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61109d80620004986000396000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80638d5b87b6116100ee578063a3106b9511610097578063d547741f11610071578063d547741f146103df578063dd62ed3e146103f2578063e6293e231461042b578063f6e396401461043e57600080fd5b8063a3106b9514610392578063a9059cbb146103a5578063d5391393146103b857600080fd5b80639dc29fac116100c85780639dc29fac146103645780639df806d614610377578063a217fddf1461038a57600080fd5b80638d5b87b61461031057806391d148541461032357806395d89b411461035c57600080fd5b8063282c51f31161015b57806334d722c91161013557806334d722c91461029657806336568abe146102c157806340c10f19146102d457806370a08231146102e757600080fd5b8063282c51f31461024b5780632f2ff15d14610272578063313ce5671461028757600080fd5b806318160ddd1161018c57806318160ddd1461020357806323b872dd14610215578063248a9ca31461022857600080fd5b806301ffc9a7146101b357806306fdde03146101db578063095ea7b3146101f0575b600080fd5b6101c66101c1366004610e68565b610451565b60405190151581526020015b60405180910390f35b6101e36104ea565b6040516101d29190610eb1565b6101c66101fe366004610f1c565b61057c565b6002545b6040519081526020016101d2565b6101c6610223366004610f46565b610594565b610207610236366004610f82565b60009081526005602052604090206001015490565b6102077f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84881565b610285610280366004610f9b565b6105b8565b005b604051601281526020016101d2565b6007546102a9906001600160a01b031681565b6040516001600160a01b0390911681526020016101d2565b6102856102cf366004610f9b565b6105e3565b6102856102e2366004610f1c565b610634565b6102076102f5366004610fc7565b6001600160a01b031660009081526020819052604090205490565b6006546102a9906001600160a01b031681565b6101c6610331366004610f9b565b60009182526005602090815260408084206001600160a01b0393909316845291905290205460ff1690565b6101e3610668565b610285610372366004610f1c565b610677565b610285610385366004610fc7565b61074d565b610207600081565b6102856103a0366004610fc7565b6107e2565b6101c66103b3366004610f1c565b610877565b6102077f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102856103ed366004610f9b565b610885565b610207610400366004610fe2565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6008546102a9906001600160a01b031681565b61028561044c366004610fc7565b6108aa565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7965db0b0000000000000000000000000000000000000000000000000000000014806104e457507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b6060600380546104f99061100c565b80601f01602080910402602001604051908101604052809291908181526020018280546105259061100c565b80156105725780601f1061054757610100808354040283529160200191610572565b820191906000526020600020905b81548152906001019060200180831161055557829003601f168201915b5050505050905090565b60003361058a818585610901565b5060019392505050565b6000336105a285828561090e565b6105ad85858561099f565b506001949350505050565b6000828152600560205260409020600101546105d3816109fe565b6105dd8383610a0b565b50505050565b6001600160a01b0381163314610625576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61062f8282610ab9565b505050565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a661065e816109fe565b61062f8383610b40565b6060600480546104f99061100c565b7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a8486106a1816109fe565b6006546001600160a01b03848116911614610743576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4275726e696e67206f6e6c7920616c6c6f776564206f6e20736572766963652060448201527f6163636f756e740000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61062f8383610b7a565b6000610758816109fe565b60085461078f907f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848906001600160a01b0316610ab9565b506008805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84883610a0b565b60006107ed816109fe565b600754610824907f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6906001600160a01b0316610ab9565b506007805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a683610a0b565b60003361058a81858561099f565b6000828152600560205260409020600101546108a0816109fe565b6105dd8383610ab9565b60006108b5816109fe565b6006546108cd906000906001600160a01b0316610ab9565b506006805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f600083610a0b565b61062f8383836001610bb0565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981146105dd5781811015610990576040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602481018290526044810183905260640161073a565b6105dd84848484036000610bb0565b6001600160a01b0383166109c957604051634b637e8f60e11b81526000600482015260240161073a565b6001600160a01b0382166109f35760405163ec442f0560e01b81526000600482015260240161073a565b61062f838383610cb7565b610a088133610dfa565b50565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff16610ab15760008381526005602090815260408083206001600160a01b03861684529091529020805460ff19166001179055610a693390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45060016104e4565b5060006104e4565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff1615610ab15760008381526005602090815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45060016104e4565b6001600160a01b038216610b6a5760405163ec442f0560e01b81526000600482015260240161073a565b610b7660008383610cb7565b5050565b6001600160a01b038216610ba457604051634b637e8f60e11b81526000600482015260240161073a565b610b7682600083610cb7565b6001600160a01b038416610bf3576040517fe602df050000000000000000000000000000000000000000000000000000000081526000600482015260240161073a565b6001600160a01b038316610c36576040517f94280d620000000000000000000000000000000000000000000000000000000081526000600482015260240161073a565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156105dd57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610ca991815260200190565b60405180910390a350505050565b6001600160a01b038316610ce2578060026000828254610cd79190611046565b90915550610d6d9050565b6001600160a01b03831660009081526020819052604090205481811015610d4e576040517fe450d38c0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602481018290526044810183905260640161073a565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d8957600280548290039055610da8565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ded91815260200190565b60405180910390a3505050565b60008281526005602090815260408083206001600160a01b038516845290915290205460ff16610b76576040517fe2517d3f0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810183905260440161073a565b600060208284031215610e7a57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610eaa57600080fd5b9392505050565b60006020808352835180602085015260005b81811015610edf57858101830151858201604001528201610ec3565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610f1757600080fd5b919050565b60008060408385031215610f2f57600080fd5b610f3883610f00565b946020939093013593505050565b600080600060608486031215610f5b57600080fd5b610f6484610f00565b9250610f7260208501610f00565b9150604084013590509250925092565b600060208284031215610f9457600080fd5b5035919050565b60008060408385031215610fae57600080fd5b82359150610fbe60208401610f00565b90509250929050565b600060208284031215610fd957600080fd5b610eaa82610f00565b60008060408385031215610ff557600080fd5b610ffe83610f00565b9150610fbe60208401610f00565b600181811c9082168061102057607f821691505b60208210810361104057634e487b7160e01b600052602260045260246000fd5b50919050565b808201808211156104e457634e487b7160e01b600052601160045260246000fdfea2646970667358221220dfc176463da83bc79bdb555993d34677afd1137fdd7c0faff5bed07ae3fc4e9f64736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101ae5760003560e01c80638d5b87b6116100ee578063a3106b9511610097578063d547741f11610071578063d547741f146103df578063dd62ed3e146103f2578063e6293e231461042b578063f6e396401461043e57600080fd5b8063a3106b9514610392578063a9059cbb146103a5578063d5391393146103b857600080fd5b80639dc29fac116100c85780639dc29fac146103645780639df806d614610377578063a217fddf1461038a57600080fd5b80638d5b87b61461031057806391d148541461032357806395d89b411461035c57600080fd5b8063282c51f31161015b57806334d722c91161013557806334d722c91461029657806336568abe146102c157806340c10f19146102d457806370a08231146102e757600080fd5b8063282c51f31461024b5780632f2ff15d14610272578063313ce5671461028757600080fd5b806318160ddd1161018c57806318160ddd1461020357806323b872dd14610215578063248a9ca31461022857600080fd5b806301ffc9a7146101b357806306fdde03146101db578063095ea7b3146101f0575b600080fd5b6101c66101c1366004610e68565b610451565b60405190151581526020015b60405180910390f35b6101e36104ea565b6040516101d29190610eb1565b6101c66101fe366004610f1c565b61057c565b6002545b6040519081526020016101d2565b6101c6610223366004610f46565b610594565b610207610236366004610f82565b60009081526005602052604090206001015490565b6102077f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84881565b610285610280366004610f9b565b6105b8565b005b604051601281526020016101d2565b6007546102a9906001600160a01b031681565b6040516001600160a01b0390911681526020016101d2565b6102856102cf366004610f9b565b6105e3565b6102856102e2366004610f1c565b610634565b6102076102f5366004610fc7565b6001600160a01b031660009081526020819052604090205490565b6006546102a9906001600160a01b031681565b6101c6610331366004610f9b565b60009182526005602090815260408084206001600160a01b0393909316845291905290205460ff1690565b6101e3610668565b610285610372366004610f1c565b610677565b610285610385366004610fc7565b61074d565b610207600081565b6102856103a0366004610fc7565b6107e2565b6101c66103b3366004610f1c565b610877565b6102077f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6102856103ed366004610f9b565b610885565b610207610400366004610fe2565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6008546102a9906001600160a01b031681565b61028561044c366004610fc7565b6108aa565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7965db0b0000000000000000000000000000000000000000000000000000000014806104e457507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b6060600380546104f99061100c565b80601f01602080910402602001604051908101604052809291908181526020018280546105259061100c565b80156105725780601f1061054757610100808354040283529160200191610572565b820191906000526020600020905b81548152906001019060200180831161055557829003601f168201915b5050505050905090565b60003361058a818585610901565b5060019392505050565b6000336105a285828561090e565b6105ad85858561099f565b506001949350505050565b6000828152600560205260409020600101546105d3816109fe565b6105dd8383610a0b565b50505050565b6001600160a01b0381163314610625576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61062f8282610ab9565b505050565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a661065e816109fe565b61062f8383610b40565b6060600480546104f99061100c565b7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a8486106a1816109fe565b6006546001600160a01b03848116911614610743576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4275726e696e67206f6e6c7920616c6c6f776564206f6e20736572766963652060448201527f6163636f756e740000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61062f8383610b7a565b6000610758816109fe565b60085461078f907f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848906001600160a01b0316610ab9565b506008805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f7f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84883610a0b565b60006107ed816109fe565b600754610824907f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6906001600160a01b0316610ab9565b506007805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a683610a0b565b60003361058a81858561099f565b6000828152600560205260409020600101546108a0816109fe565b6105dd8383610ab9565b60006108b5816109fe565b6006546108cd906000906001600160a01b0316610ab9565b506006805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03841617905561062f600083610a0b565b61062f8383836001610bb0565b6001600160a01b0383811660009081526001602090815260408083209386168352929052205460001981146105dd5781811015610990576040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602481018290526044810183905260640161073a565b6105dd84848484036000610bb0565b6001600160a01b0383166109c957604051634b637e8f60e11b81526000600482015260240161073a565b6001600160a01b0382166109f35760405163ec442f0560e01b81526000600482015260240161073a565b61062f838383610cb7565b610a088133610dfa565b50565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff16610ab15760008381526005602090815260408083206001600160a01b03861684529091529020805460ff19166001179055610a693390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45060016104e4565b5060006104e4565b60008281526005602090815260408083206001600160a01b038516845290915281205460ff1615610ab15760008381526005602090815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45060016104e4565b6001600160a01b038216610b6a5760405163ec442f0560e01b81526000600482015260240161073a565b610b7660008383610cb7565b5050565b6001600160a01b038216610ba457604051634b637e8f60e11b81526000600482015260240161073a565b610b7682600083610cb7565b6001600160a01b038416610bf3576040517fe602df050000000000000000000000000000000000000000000000000000000081526000600482015260240161073a565b6001600160a01b038316610c36576040517f94280d620000000000000000000000000000000000000000000000000000000081526000600482015260240161073a565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156105dd57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610ca991815260200190565b60405180910390a350505050565b6001600160a01b038316610ce2578060026000828254610cd79190611046565b90915550610d6d9050565b6001600160a01b03831660009081526020819052604090205481811015610d4e576040517fe450d38c0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602481018290526044810183905260640161073a565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216610d8957600280548290039055610da8565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610ded91815260200190565b60405180910390a3505050565b60008281526005602090815260408083206001600160a01b038516845290915290205460ff16610b76576040517fe2517d3f0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810183905260440161073a565b600060208284031215610e7a57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610eaa57600080fd5b9392505050565b60006020808352835180602085015260005b81811015610edf57858101830151858201604001528201610ec3565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610f1757600080fd5b919050565b60008060408385031215610f2f57600080fd5b610f3883610f00565b946020939093013593505050565b600080600060608486031215610f5b57600080fd5b610f6484610f00565b9250610f7260208501610f00565b9150604084013590509250925092565b600060208284031215610f9457600080fd5b5035919050565b60008060408385031215610fae57600080fd5b82359150610fbe60208401610f00565b90509250929050565b600060208284031215610fd957600080fd5b610eaa82610f00565b60008060408385031215610ff557600080fd5b610ffe83610f00565b9150610fbe60208401610f00565b600181811c9082168061102057607f821691505b60208210810361104057634e487b7160e01b600052602260045260246000fd5b50919050565b808201808211156104e457634e487b7160e01b600052601160045260246000fdfea2646970667358221220dfc176463da83bc79bdb555993d34677afd1137fdd7c0faff5bed07ae3fc4e9f64736f6c63430008180033", - "linkReferences": {}, - "deployedLinkReferences": {} -} \ No newline at end of file diff --git a/bca-token-market/src/lib/contracts.ts b/bca-token-market/src/lib/contracts.ts index ab7f6d6..c77b60f 100644 --- a/bca-token-market/src/lib/contracts.ts +++ b/bca-token-market/src/lib/contracts.ts @@ -1,14 +1,23 @@ // // local hardhat node - testnet import deployed_address from "$lib/../../../bca-token-solidity/ignition/deployments/chain-31337/deployed_addresses.json" +// import abi_token_json from "$lib/../../../bca-token-solidity/ignition/deployments/chain-31337/artifacts/BCA_Token#BCAServiceToken.json" +// import abi_servicemanager_json from "$lib/../../../bca-token-solidity/ignition/deployments/chain-31337/artifacts/BCA_ServiceManager#BCAServiceManager.json" -// // Amoy - Polygon testnet +// // Amoy - Polygon testnet // import deployed_address from "$lib/../../../bca-token-solidity/ignition/deployments/chain-80002/deployed_addresses.json" export const tokenContractAddress: string = deployed_address["BCA_Token#BCAServiceToken"] +export const serviceManagerAddress: string = deployed_address["BCA_ServiceManager#BCAServiceManager"] export const contractAddresses: Record = deployed_address import abi_token_json from "$lib/bca_token-abi.json" export const tokenContractABI = abi_token_json["abi"]; +import abi_servicemanager_json from "$lib/bca_servicemanager-abi.json" +export const serviceManagerABI = abi_servicemanager_json["abi"]; +import abi_servicecontroller_json from "$lib/bca_servicecontroller-abi.json" +export const serviceControllerABI = abi_servicecontroller_json["abi"]; import abi_service_json from "$lib/bca_service-abi.json" -export const serviceContractABI = abi_service_json["abi"]; +export const serviceABI = abi_service_json["abi"]; +import abi_serviceinstance_json from "$lib/bca_serviceinstance-abi.json" +export const serviceInstanceABI = abi_serviceinstance_json["abi"]; diff --git a/bca-token-market/src/routes/service/+page.server.ts b/bca-token-market/src/routes/service/+page.server.ts new file mode 100644 index 0000000..4794fd1 --- /dev/null +++ b/bca-token-market/src/routes/service/+page.server.ts @@ -0,0 +1,12 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const session = await event.locals.auth() + + if (!session?.user?.id) { + throw redirect(303, '/') + } + + return { session } + }; diff --git a/bca-token-market/src/routes/service/[addr]/+page.server.ts b/bca-token-market/src/routes/service/[addr]/+page.server.ts new file mode 100644 index 0000000..f6ae3c3 --- /dev/null +++ b/bca-token-market/src/routes/service/[addr]/+page.server.ts @@ -0,0 +1,5 @@ +export function load({ params }) { + return { + addr: params.addr + }; +} \ No newline at end of file diff --git a/bca-token-market/src/routes/service/[addr]/+page.svelte b/bca-token-market/src/routes/service/[addr]/+page.svelte new file mode 100644 index 0000000..cec148a --- /dev/null +++ b/bca-token-market/src/routes/service/[addr]/+page.svelte @@ -0,0 +1,160 @@ + + +
+

Service

+ + {#if $page.data.session && is_provider} + +

Service - {data.addr}

+ + + + + + {#if details !== undefined && wallet.walletaddr !== undefined} + +

number of instances: {details.ninstances}

+

max instances: {details.maxinstances}

+

available instances: {details.maxinstances - details.ninstances} ({(details.maxinstances - details.ninstances) * 100 / details.maxinstances}%)

+ {#each details.instanceAddresses as instanceAddress,idx} +

instance {idx} @ {instanceAddress}

+ {/each} + +
+

Create instance

+
+ + + {#if wallet.walletnetwork !== "0x89" } + + + {/if} + +
+
+ {/if} + + {/if} +
+ + \ No newline at end of file diff --git a/bca-token-market/src/routes/servicecontroller/+page.server.ts b/bca-token-market/src/routes/servicecontroller/+page.server.ts new file mode 100644 index 0000000..4794fd1 --- /dev/null +++ b/bca-token-market/src/routes/servicecontroller/+page.server.ts @@ -0,0 +1,12 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const session = await event.locals.auth() + + if (!session?.user?.id) { + throw redirect(303, '/') + } + + return { session } + }; diff --git a/bca-token-market/src/routes/servicecontroller/[addr]/+page.server.ts b/bca-token-market/src/routes/servicecontroller/[addr]/+page.server.ts new file mode 100644 index 0000000..f6ae3c3 --- /dev/null +++ b/bca-token-market/src/routes/servicecontroller/[addr]/+page.server.ts @@ -0,0 +1,5 @@ +export function load({ params }) { + return { + addr: params.addr + }; +} \ No newline at end of file diff --git a/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte b/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte new file mode 100644 index 0000000..c4a9987 --- /dev/null +++ b/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte @@ -0,0 +1,169 @@ + + +
+

Service Controller

+ + {#if $page.data.session && is_provider} + +

Service Controller - {data.addr}

+ + + + + + {#if details !== undefined && wallet.walletaddr !== undefined} + +

number of services: {details.nservices}

+ {#each details.serviceAddresses as serviceAddress,idx} +

service {idx} @ {serviceAddress}

+ {/each} + +
+

Create service

+
+ + + + + {#if wallet.walletnetwork !== "0x89" } + + + {/if} + +
+
+ {/if} + + {/if} +
+ + \ No newline at end of file diff --git a/bca-token-market/src/routes/serviceinstance/+page.server.ts b/bca-token-market/src/routes/serviceinstance/+page.server.ts new file mode 100644 index 0000000..4794fd1 --- /dev/null +++ b/bca-token-market/src/routes/serviceinstance/+page.server.ts @@ -0,0 +1,12 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const session = await event.locals.auth() + + if (!session?.user?.id) { + throw redirect(303, '/') + } + + return { session } + }; diff --git a/bca-token-market/src/routes/serviceinstance/[addr]/+page.server.ts b/bca-token-market/src/routes/serviceinstance/[addr]/+page.server.ts new file mode 100644 index 0000000..f6ae3c3 --- /dev/null +++ b/bca-token-market/src/routes/serviceinstance/[addr]/+page.server.ts @@ -0,0 +1,5 @@ +export function load({ params }) { + return { + addr: params.addr + }; +} \ No newline at end of file diff --git a/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte new file mode 100644 index 0000000..ea6c1b4 --- /dev/null +++ b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte @@ -0,0 +1,276 @@ + + +
+ + {#if $page.data.session} + +

Service Instance - {data.addr}

+ + + + + + {#if details !== undefined && wallet.walletaddr !== undefined} +

daily price: {details.dayPrice / 10**18}

+

user deposit: {details.deposit / 10**18}

+

retracted: {details.retracted / 10**18}

+

start time: {details.startTime}

+

end time: {details.endTime}

+

provider address: {details.providerAddress}

+

user address: {details.userAddress}

+ {/if} + +
+

Withdrawal from contract

+
+ + + {#if wallet.walletnetwork !== "0x89" } + + + {/if} + +
+
+ {#if ! is_provider} +
+

Deposit to contract

+
+ + + {#if wallet.walletnetwork !== "0x89" } + + + {/if} + +
+
+ {/if} + + {/if} +
+ + \ No newline at end of file diff --git a/bca-token-market/src/routes/servicemanager/+page.server.ts b/bca-token-market/src/routes/servicemanager/+page.server.ts new file mode 100644 index 0000000..4794fd1 --- /dev/null +++ b/bca-token-market/src/routes/servicemanager/+page.server.ts @@ -0,0 +1,12 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async (event) => { + const session = await event.locals.auth() + + if (!session?.user?.id) { + throw redirect(303, '/') + } + + return { session } + }; diff --git a/bca-token-market/src/routes/servicemanager/+page.svelte b/bca-token-market/src/routes/servicemanager/+page.svelte new file mode 100644 index 0000000..cc4e1c5 --- /dev/null +++ b/bca-token-market/src/routes/servicemanager/+page.svelte @@ -0,0 +1,17 @@ + + +
+

Service Manager

+ + {#if $page.data.session && is_provider} + +

Service Manager

+

Service Manager

+ + {/if} +
\ No newline at end of file diff --git a/bca-token-market/src/routes/servicemanager/[addr]/+page.server.ts b/bca-token-market/src/routes/servicemanager/[addr]/+page.server.ts new file mode 100644 index 0000000..f6ae3c3 --- /dev/null +++ b/bca-token-market/src/routes/servicemanager/[addr]/+page.server.ts @@ -0,0 +1,5 @@ +export function load({ params }) { + return { + addr: params.addr + }; +} \ No newline at end of file diff --git a/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte b/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte new file mode 100644 index 0000000..e9af0dc --- /dev/null +++ b/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte @@ -0,0 +1,158 @@ + + +
+

Service Manager

+ + {#if $page.data.session && is_provider} + +

Service Manager - {data.addr}

+ + + + + + {#if details !== undefined && wallet.walletaddr !== undefined} + {#if details.ncontrollers > 0} +

number of controllers: {details.ncontrollers}

+

provider address: {details.providerAddress}

+

controller address: {details.controllerAddress}

+

controller

+ {:else} +
+

Create controller

+
+ + + {#if wallet.walletnetwork !== "0x89" } + + + {/if} + +
+
+ {/if} + + {/if} + + {/if} +
+ + \ No newline at end of file diff --git a/bca-token-market/update-abi.sh b/bca-token-market/update-abi.sh new file mode 100755 index 0000000..1983e4f --- /dev/null +++ b/bca-token-market/update-abi.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +SRCPATH="../bca-token-solidity/artifacts/contracts" +cp -v ${SRCPATH}/BCA_Service.sol/BCAService.json src/lib/bca_service-abi.json +cp -v ${SRCPATH}/BCA_ServiceInstance.sol/BCAServiceInstance.json src/lib/bca_serviceinstance-abi.json +cp -v ${SRCPATH}/BCA_ServiceController.sol/BCAServiceController.json src/lib/bca_servicecontroller-abi.json + +SRCPATH="../bca-token-solidity/ignition/deployments/chain-31337/artifacts" +cp -v ${SRCPATH}/BCA_Token#BCAServiceToken.json src/lib/bca_token-abi.json +cp -v ${SRCPATH}/BCA_ServiceManager#BCAServiceManager.json src/lib/bca_servicemanager-abi.json diff --git a/bca-token-solidity/contracts/BCA_ServiceManager.sol b/bca-token-solidity/contracts/BCA_ServiceManager.sol index 5f4fde3..1025ac6 100644 --- a/bca-token-solidity/contracts/BCA_ServiceManager.sol +++ b/bca-token-solidity/contracts/BCA_ServiceManager.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./Iface_ServiceManager.sol"; import "./BCA_ServiceController.sol"; -// Factory contract that deploys service contracts +// Factory contract that deploys controller contracts contract BCAServiceManager is IServiceManager, ReentrancyGuard { using SafeERC20 for IERC20; @@ -18,7 +18,7 @@ contract BCAServiceManager is IServiceManager, ReentrancyGuard { bool isDeployed; } mapping (address => ControllerStruct) public deployedControllers; - address[] public addressControllers; + address[] public providerControllers; // Event to notify when a new service is deployed event ControllerDeployed(address contractAddress); @@ -35,7 +35,7 @@ contract BCAServiceManager is IServiceManager, ReentrancyGuard { } function isDeployed(address providerAddress) public view returns(bool isdeployed) { - if (addressControllers.length == 0) return false; + if (providerControllers.length == 0) return false; return (deployedControllers[providerAddress].isDeployed); } @@ -46,7 +46,7 @@ contract BCAServiceManager is IServiceManager, ReentrancyGuard { BCAServiceController controllerContract = new BCAServiceController(providerAddress, address(tokToken)); // Store the address - addressControllers.push(address(controllerContract)); + providerControllers.push(providerAddress); deployedControllers[providerAddress].addrContract = address(controllerContract); deployedControllers[providerAddress].isDeployed = true; @@ -55,6 +55,6 @@ contract BCAServiceManager is IServiceManager, ReentrancyGuard { } function countServiceControllers() public view returns (uint) { - return addressControllers.length; + return providerControllers.length; } } \ No newline at end of file From ea60b6cf997f70686246d7d860a32d7135064ae6 Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Thu, 21 Nov 2024 23:50:42 +0100 Subject: [PATCH 11/12] improved UI Signed-off-by: Alexander Diemand --- bca-token-market/src/lib/contracts.ts | 18 ++ .../serviceinstance/[addr]/+page.svelte | 204 ++++++++++++++++-- 2 files changed, 207 insertions(+), 15 deletions(-) diff --git a/bca-token-market/src/lib/contracts.ts b/bca-token-market/src/lib/contracts.ts index c77b60f..1d8776a 100644 --- a/bca-token-market/src/lib/contracts.ts +++ b/bca-token-market/src/lib/contracts.ts @@ -21,3 +21,21 @@ import abi_service_json from "$lib/bca_service-abi.json" export const serviceABI = abi_service_json["abi"]; import abi_serviceinstance_json from "$lib/bca_serviceinstance-abi.json" export const serviceInstanceABI = abi_serviceinstance_json["abi"]; + +// token constants +export const token_symbol: string = 'BCA1' +export const token_decimals: number = 18 + +// calculations +export function calculate_user_balance(deposit: number, startTime: number, dayPrice: number) { + if (startTime <= 0) { return deposit } + const now = new Date().getTime() + const deltaMilsecs = now - startTime * 1000 + return Math.max(0, deposit - (dayPrice * deltaMilsecs / 24 / 3600 / 1000)) +} +export function calculate_provider_balance(deposit: number, retracted: number, startTime: number, dayPrice: number) { + if (startTime <= 0) { return deposit } + const now = new Date().getTime() + const deltaMilsecs = now - startTime * 1000 + return Math.max(0, Math.min(deposit, dayPrice * deltaMilsecs / 24 / 3600 / 1000) - retracted) +} diff --git a/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte index ea6c1b4..245d2c6 100644 --- a/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte +++ b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte' import { createForm } from "svelte-forms-lib"; import { Contract, Web3 } from 'web3' - import { serviceInstanceABI, tokenContractABI } from "$lib/contracts.js" + import { serviceInstanceABI, tokenContractABI, token_symbol, token_decimals, calculate_user_balance, calculate_provider_balance } from "$lib/contracts.js" import { WalletInformation, reset_warning, get_wallet_addr, wallet_logout } from '$lib/wallet' import { number } from "zod"; @@ -14,11 +14,14 @@ onMount ( async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum); + local_get_wallet_addr({target: undefined}) } else { wallet.warning = "no web3 wallet attached!" } }) + $: has_wallet = !!wallet.walletaddr + const is_provider = $page.data.session?.user?.role == "Provider" async function local_get_wallet_addr(ev) { @@ -31,6 +34,18 @@ reset_warning(wallet); } + const locale = 'en' // better get this from the browser + const options: Intl.DateTimeFormatOptions = { + weekday: undefined, + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + } + const date_formatter = new Intl.DateTimeFormat(locale, options) + let details: { dayPrice: number; deposit: number; retracted: number; startTime: number; endTime: number; userAddress: string; providerAddress: string; tokenAddress: string } | undefined = undefined async function read_contract(contractAddress: string) { if (window.web3 && wallet.walletaddr && contractAddress) { @@ -55,12 +70,43 @@ return this.toString(); }; + let contractevents = undefined //: { event: string, blockNumber: string, blockHash: string, transactionHash: string, address: string, returnValues: Record, topics: string[] }[] | undefined = undefined + async function list_events() { + if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + let contract: Contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr) + contract.getPastEvents('ALLEVENTS', { fromBlock: 0, toBlock: 'latest'}).then(function (events) { + if (events.length) { + contractevents = events + // console.log(JSON.stringify(events,null,2)) + } + }) + } else { + contractevents = undefined + } + } + async function get_transaction(txhash: string) { + if (window.web3 && txhash) { + window.web3.eth.getTransaction(txhash).then((tx) => console.log(JSON.stringify(tx,null,2))) + } + } + async function get_block_time(blockHash: string): Promise { + if (window.web3 && blockHash) { + // console.log(`get_block_time ${blockHash}`) + const bdata = await window.web3.eth.getBlock(blockHash, false) + if (bdata && bdata.timestamp) { + // console.log(`block data: ${JSON.stringify(bdata,null,2)}`) + return BigInt(bdata.timestamp) + } + return 0n + } + return 0n + } async function withdraw_user(amount: number, useGas: number) { if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { - const one_token: number = 10**18; + const one_token: number = 10**(token_decimals); const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon @@ -87,7 +133,7 @@ const contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { - const one_token: number = 10**18; + const one_token: number = 10**(token_decimals); const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon @@ -187,39 +233,89 @@
{#if $page.data.session} + +

 

+ +

Service Instance

+ +

Details

-

Service Instance - {data.addr}

+ + +
+
    +
  • Contract address: {data.addr}
  • + {#if wallet.walletnetwork === "0x89"} +
  • PolygonScan: view
  • + {:else if wallet.walletnetwork === "0x80002"} +
  • PolygonScan: view
  • + {/if} +
+
+ +


+


+


+


+


+


+


+


+ +

 

+ +

Service Instance

+ +

Contract

- + + +
+

{#if details !== undefined && wallet.walletaddr !== undefined} -

daily price: {details.dayPrice / 10**18}

-

user deposit: {details.deposit / 10**18}

-

retracted: {details.retracted / 10**18}

-

start time: {details.startTime}

-

end time: {details.endTime}

-

provider address: {details.providerAddress}

-

user address: {details.userAddress}

+
    +
  • daily price: {details.dayPrice / 10**(token_decimals)} {token_symbol}
  • +
  • user deposit: {details.deposit / 10**(token_decimals)} {token_symbol}
  • +
  • retracted: {details.retracted / 10**(token_decimals)} {token_symbol}
  • +
  • start time: { details.startTime > 0 ? date_formatter.format(details.startTime * 1000) : '' }
  • +
  • end time: {details.endTime > 0 ? date_formatter.format(details.endTime * 1000) : (details.startTime > 0 ? "(" + date_formatter.format(details.startTime * 1000 + (details.deposit * 1000 * 24 * 3600 / details.dayPrice)) + ") estimated" : '') }
  • +
  • provider address: {details.providerAddress}
  • +
  • user address: {details.userAddress}
  • +
  • estimated user balance: { calculate_user_balance(details.deposit, details.startTime, details.dayPrice) / 10**(token_decimals)} {token_symbol}
  • +
  • estimated provider balance: { calculate_provider_balance(details.deposit, details.retracted, details.startTime, details.dayPrice) / 10**(token_decimals)} {token_symbol}
  • +
{/if}

Withdrawal from contract

- +
{/if} +
+ +


+


+


+


+


+


+


+


+ +

 

+ +

Service Instance

+

Events

+ + + +
+

+ + {#if contractevents && contractevents.length} + {#each contractevents as cev} +

{cev.event ? cev.event : "some event"}

+ + + + + + + +
Timestamp: + {#await get_block_time(cev.blockHash) then timestamp} + {date_formatter.format(Number(timestamp * 1000n))} + {:catch} + error + {/await} +
Block:#{cev.blockNumber ? cev.blockNumber : "#?"} {cev.blockHash ? cev.blockHash : "0x.."}
Transaction: + {#if cev.transactionHash} + + {:else} + "no transaction info" + {/if} +
Address:{cev.address ? cev.address : "0x.."}
Topics:{cev.topics ? JSON.stringify(cev.topics,null,2) : "[ ]"}
Values:{cev.returnValues ? JSON.stringify(cev.returnValues,null,2) : "{ }"}
+ {/each} + {/if}
+ +


+


+


+


+


+


+


+


+ +

 

+ +

Service Instance

+

Transactions

+ + + +
+


+


+


+
{/if}
From b2c74a1ccdaf905fdeef600ee1c45352e49c688d Mon Sep 17 00:00:00 2001 From: Alexander Diemand Date: Fri, 22 Nov 2024 22:05:37 +0100 Subject: [PATCH 12/12] beautified and roughly tested Signed-off-by: Alexander Diemand --- bca-token-app/src/lib/wallet.ts | 6 +- bca-token-market/src/lib/contracts.ts | 16 +++ bca-token-market/src/lib/wallet.ts | 4 +- bca-token-market/src/routes/+layout.svelte | 8 +- bca-token-market/src/routes/+page.svelte | 39 +++++- .../src/routes/service/[addr]/+page.svelte | 39 +++--- .../servicecontroller/[addr]/+page.svelte | 46 ++++--- .../serviceinstance/[addr]/+page.svelte | 112 +++++++++++------- .../src/routes/servicemanager/+page.svelte | 2 +- .../routes/servicemanager/[addr]/+page.svelte | 50 ++++---- 10 files changed, 204 insertions(+), 118 deletions(-) diff --git a/bca-token-app/src/lib/wallet.ts b/bca-token-app/src/lib/wallet.ts index 4199f20..9be9d56 100644 --- a/bca-token-app/src/lib/wallet.ts +++ b/bca-token-app/src/lib/wallet.ts @@ -56,7 +56,7 @@ export async function get_wallet_balance(wallet: WalletInformation, ev: any): Pr // await contract.methods.decimals().call().then(console.log); // const decimals = await contract.methods.decimals().call(); // const decimals = 10; - console.log("decimals = " + decimals) + // console.log("decimals = " + decimals) wallet.walletbalance = Number(await window.web3.eth.getBalance(wallet.walletaddr)); if (!!wallet.walletbalance) { wallet.walletbalance = wallet.walletbalance / (10 ** decimals) } wallet.warning = undefined @@ -85,8 +85,8 @@ export async function get_wallet_addr(wallet: WalletInformation, ev: any): Promi wallet.walletaddr = selectedAccount; wallet.warning = undefined - console.log("address: " + wallet.walletaddr) - console.log("network: " + wallet.walletnetwork) + // console.log("address: " + wallet.walletaddr) + // console.log("network: " + wallet.walletnetwork) } catch (error) { wallet.warning = "error while accessing wallet: " + error; diff --git a/bca-token-market/src/lib/contracts.ts b/bca-token-market/src/lib/contracts.ts index 1d8776a..2dd1678 100644 --- a/bca-token-market/src/lib/contracts.ts +++ b/bca-token-market/src/lib/contracts.ts @@ -39,3 +39,19 @@ export function calculate_provider_balance(deposit: number, retracted: number, s const deltaMilsecs = now - startTime * 1000 return Math.max(0, Math.min(deposit, dayPrice * deltaMilsecs / 24 / 3600 / 1000) - retracted) } + +// chain viewer url +export function mk_chainviewer_url(address: string, network: string|undefined): string { + if (network === "0x89") { + return `${address} on Polygon network: ${network} ` + } else if (network === "0x80002") { + return `${address} on Polygon's Amoy network: ${network} ` + } else { + return `${address} on network: ${network} ` + } +} + +// utilities +export function shorten_address(address: string, len: number = 6) { + return address.substring(0,len) + ".." + address.substring(address.length - len + 1) +} diff --git a/bca-token-market/src/lib/wallet.ts b/bca-token-market/src/lib/wallet.ts index b21be54..f560ea4 100644 --- a/bca-token-market/src/lib/wallet.ts +++ b/bca-token-market/src/lib/wallet.ts @@ -63,8 +63,8 @@ export async function get_wallet_addr(wallet: WalletInformation, ev: any): Promi wallet.walletaddr = selectedAccount; wallet.warning = undefined - console.log("address: " + wallet.walletaddr) - console.log("network: " + wallet.walletnetwork) + // console.log("address: " + wallet.walletaddr) + // console.log("network: " + wallet.walletnetwork) } catch (error) { wallet.warning = "error while accessing wallet: " + error; diff --git a/bca-token-market/src/routes/+layout.svelte b/bca-token-market/src/routes/+layout.svelte index 241f07e..c87facb 100644 --- a/bca-token-market/src/routes/+layout.svelte +++ b/bca-token-market/src/routes/+layout.svelte @@ -5,12 +5,6 @@ @@ -18,7 +12,7 @@

- +

Powered by BCA Blockchain Analytics BCA

\ No newline at end of file diff --git a/bca-token-market/src/routes/+page.svelte b/bca-token-market/src/routes/+page.svelte index ad03b58..fd4ce60 100644 --- a/bca-token-market/src/routes/+page.svelte +++ b/bca-token-market/src/routes/+page.svelte @@ -1,6 +1,33 @@
@@ -21,9 +48,15 @@

{$page.data.session.user?.name ?? "User"} {$page.data.session.user?.email ?? "Email"}

- -
Sign out
-
+ {#if has_wallet} +

Wallet:

+ {#if wallet && wallet.walletaddr} +

Address: {@html mk_chainviewer_url(wallet.walletaddr, wallet.walletnetwork)}

+ {/if} +

Continue to this system's Service Manager

+ {:else} +

Wallet:

+ {/if} {:else}

You are not signed in

diff --git a/bca-token-market/src/routes/service/[addr]/+page.svelte b/bca-token-market/src/routes/service/[addr]/+page.svelte index cec148a..3566522 100644 --- a/bca-token-market/src/routes/service/[addr]/+page.svelte +++ b/bca-token-market/src/routes/service/[addr]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte' import { createForm } from "svelte-forms-lib"; import { Contract, Web3 } from 'web3' - import { serviceABI } from "$lib/contracts.js" + import { serviceABI, shorten_address } from "$lib/contracts.js" import { WalletInformation, reset_warning, get_wallet_addr, wallet_logout } from '$lib/wallet' export let data @@ -13,11 +13,17 @@ onMount ( async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum); + await local_get_wallet_addr({target: undefined}) + if (wallet.walletaddr && data && data.addr) { + await read_contract() + } } else { wallet.warning = "no web3 wallet attached!" } }) + $: has_wallet = !!wallet.walletaddr + const is_provider = $page.data.session?.user?.role == "Provider" async function local_get_wallet_addr(ev) { @@ -31,10 +37,10 @@ } let details: { maxinstances: number; ninstances: number; instanceAddresses: string[]; } | undefined = undefined - async function read_contract(contractAddress: string) { - if (window.web3 && wallet.walletaddr && contractAddress) { + async function read_contract() { + if (window.web3 && has_wallet && data && data.addr) { - let contract: Contract = new window.web3.eth.Contract(serviceABI, contractAddress) + let contract: Contract = new window.web3.eth.Contract(serviceABI, data.addr) const maxinstances: number = Number(await contract.methods.maxInstances().call()) const ninstances: number = Number(await contract.methods.countServiceInstances().call()) let instanceAddresses: string[] = [] @@ -53,7 +59,7 @@ }; async function create_instance(userAddress: string, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { @@ -61,7 +67,7 @@ let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon estimatedGas = await contract.methods.newInstance(userAddress).estimateGas(); - console.log("estimated gas: " + estimatedGas); + // console.log("estimated gas: " + estimatedGas); } const receipt = await contract.methods .newInstance(userAddress) @@ -92,35 +98,36 @@

-

Service

+

Service Manager - Controller - Service

{#if $page.data.session && is_provider} -

Service - {data.addr}

+

Service - {shorten_address(data.addr)}

- +

- {#if details !== undefined && wallet.walletaddr !== undefined} + {#if details !== undefined && has_wallet}

number of instances: {details.ninstances}

max instances: {details.maxinstances}

-

available instances: {details.maxinstances - details.ninstances} ({(details.maxinstances - details.ninstances) * 100 / details.maxinstances}%)

+

available instances: {details.maxinstances - details.ninstances} ({((details.maxinstances - details.ninstances) * 100 / details.maxinstances).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %)

{#each details.instanceAddresses as instanceAddress,idx} -

instance {idx} @ {instanceAddress}

+

instance {idx+1}: {shorten_address(instanceAddress)}

{/each}
diff --git a/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte b/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte index c4a9987..96d7011 100644 --- a/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte +++ b/bca-token-market/src/routes/servicecontroller/[addr]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte' import { createForm } from "svelte-forms-lib"; import { Contract, Web3 } from 'web3' - import { serviceControllerABI } from "$lib/contracts.js" + import { mk_chainviewer_url, serviceControllerABI, shorten_address, token_decimals, token_symbol } from "$lib/contracts.js" import { WalletInformation, reset_warning, get_wallet_addr, wallet_logout } from '$lib/wallet' export let data @@ -13,11 +13,17 @@ onMount ( async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum); + await local_get_wallet_addr({target: undefined}) + if (wallet.walletaddr && data && data.addr) { + await read_contract() + } } else { wallet.warning = "no web3 wallet attached!" } }) + $: has_wallet = !!wallet.walletaddr + const is_provider = $page.data.session?.user?.role == "Provider" async function local_get_wallet_addr(ev) { @@ -31,10 +37,10 @@ } let details: { nservices: number; serviceAddresses: string[]; } | undefined = undefined - async function read_contract(contractAddress: string) { - if (window.web3 && wallet.walletaddr && contractAddress) { + async function read_contract() { + if (window.web3 && has_wallet && data && data.addr) { - let contract: Contract = new window.web3.eth.Contract(serviceControllerABI, contractAddress) + let contract: Contract = new window.web3.eth.Contract(serviceControllerABI, data.addr) const nservices: number = await contract.methods.countServiceContracts().call() let serviceAddresses: string[] = [] for (let i = 0; i < nservices; i++) { @@ -52,21 +58,21 @@ }; async function create_service(maxInstances: number, daylyPrice: float, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceControllerABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { - let dayPrice: bigint = BigInt(daylyPrice * 10 ** 18); + let dayPrice: bigint = BigInt(daylyPrice * 10 ** token_decimals); const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon - let estimatedGas = await contract.methods.newService(maxInstances, dayPrice).estimateGas(); - console.log("estimated gas: " + estimatedGas); + estimatedGas = await contract.methods.newService(maxInstances, dayPrice).estimateGas(); + // console.log("estimated gas: " + estimatedGas); } const receipt = await contract.methods .newService(maxInstances, dayPrice) .send({ - from: wallet.walletaddr, + from: wallet.walletaddr, gas: estimatedGas, gasPrice: gasPrice, }); @@ -93,34 +99,38 @@
-

Service Controller

+

Service Manager - Controller

{#if $page.data.session && is_provider} -

Service Controller - {data.addr}

+

Service Controller - {shorten_address(data.addr)}

- +

- {#if details !== undefined && wallet.walletaddr !== undefined} + {#if details !== undefined && has_wallet} +

controller address: {@html mk_chainviewer_url(data.addr, wallet.walletnetwork)}

number of services: {details.nservices}

+ {#each details.serviceAddresses as serviceAddress,idx} -

service {idx} @ {serviceAddress}

+ {/each} +
service {idx+1}: {shorten_address(serviceAddress)}

Create service

diff --git a/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte index 245d2c6..f2f355a 100644 --- a/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte +++ b/bca-token-market/src/routes/serviceinstance/[addr]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte' import { createForm } from "svelte-forms-lib"; import { Contract, Web3 } from 'web3' - import { serviceInstanceABI, tokenContractABI, token_symbol, token_decimals, calculate_user_balance, calculate_provider_balance } from "$lib/contracts.js" + import { serviceInstanceABI, tokenContractABI, token_symbol, token_decimals, calculate_user_balance, calculate_provider_balance, mk_chainviewer_url } from "$lib/contracts.js" import { WalletInformation, reset_warning, get_wallet_addr, wallet_logout } from '$lib/wallet' import { number } from "zod"; @@ -14,7 +14,10 @@ onMount ( async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum); - local_get_wallet_addr({target: undefined}) + await local_get_wallet_addr({target: undefined}) + if (wallet.walletaddr && data && data.addr) { + await read_contract() + } } else { wallet.warning = "no web3 wallet attached!" } @@ -34,23 +37,11 @@ reset_warning(wallet); } - const locale = 'en' // better get this from the browser - const options: Intl.DateTimeFormatOptions = { - weekday: undefined, - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - } - const date_formatter = new Intl.DateTimeFormat(locale, options) - let details: { dayPrice: number; deposit: number; retracted: number; startTime: number; endTime: number; userAddress: string; providerAddress: string; tokenAddress: string } | undefined = undefined - async function read_contract(contractAddress: string) { - if (window.web3 && wallet.walletaddr && contractAddress) { + async function read_contract() { + if (window.web3 && has_wallet && data && data.addr) { - let contract: Contract = new window.web3.eth.Contract(serviceInstanceABI, contractAddress) + let contract: Contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr) const dayPrice: number = Number(await contract.methods.dayPrice().call()) const deposit: number = Number(await contract.methods.deposit().call()) const retracted: number = Number(await contract.methods.retracted().call()) @@ -72,7 +63,7 @@ let contractevents = undefined //: { event: string, blockNumber: string, blockHash: string, transactionHash: string, address: string, returnValues: Record, topics: string[] }[] | undefined = undefined async function list_events() { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { let contract: Contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr) contract.getPastEvents('ALLEVENTS', { fromBlock: 0, toBlock: 'latest'}).then(function (events) { if (events.length) { @@ -102,7 +93,7 @@ return 0n } async function withdraw_user(amount: number, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { @@ -110,8 +101,8 @@ const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon - let estimatedGas = await contract.methods.withdrawUser(BigInt(amount * one_token)).estimateGas(); - console.log("estimated gas: " + estimatedGas); + estimatedGas = await contract.methods.withdrawUser(BigInt(amount * one_token)).estimateGas(); + // console.log("estimated gas: " + estimatedGas); } const receipt = await contract.methods .withdrawUser(BigInt(amount * one_token)) @@ -129,7 +120,7 @@ } } async function withdraw_provider(amount: number, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { @@ -137,8 +128,8 @@ const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon - let estimatedGas = await contract.methods.withdrawProvider(BigInt(amount * one_token)).estimateGas(); - console.log("estimated gas: " + estimatedGas); + estimatedGas = await contract.methods.withdrawProvider(BigInt(amount * one_token)).estimateGas(); + // console.log("estimated gas: " + estimatedGas); } const receipt = await contract.methods .withdrawProvider(BigInt(amount * one_token)) @@ -156,7 +147,7 @@ } } async function deposit_user(amount: number, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr && details !== undefined && details.tokenAddress) { + if (window.web3 && has_wallet && data !== undefined && data.addr && details !== undefined && details.tokenAddress) { const contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { @@ -164,7 +155,7 @@ let token: Contract = new window.web3.eth.Contract(tokenContractABI, details.tokenAddress) const decimals: bigint = await token.methods.decimals().call() - console.log(`decimals: ${decimals}`) + // console.log(`decimals: ${decimals}`) let contract: Contract = new window.web3.eth.Contract(serviceInstanceABI, data.addr) const user_addr = await contract.methods.userAddress().call() @@ -181,7 +172,7 @@ if (wallet.walletnetwork === "0x89") { // Polygon estimatedGas = Number(await token.methods.makeDeposit(BigInt(amount * one_token)).estimateGas()) - console.log(`estimated gas: ${estimatedGas}`) + // console.log(`estimated gas: ${estimatedGas}`) } contract.methods .makeDeposit(BigInt(amount * one_token)) @@ -231,9 +222,8 @@
- {#if $page.data.session} - +

 

Service Instance

@@ -249,12 +239,7 @@
    -
  • Contract address: {data.addr}
  • - {#if wallet.walletnetwork === "0x89"} -
  • PolygonScan: view
  • - {:else if wallet.walletnetwork === "0x80002"} -
  • PolygonScan: view
  • - {/if} +
  • Contract address: {@html mk_chainviewer_url(data.addr, wallet.walletnetwork) }
@@ -266,13 +251,20 @@ click the button again to update the app's state. {:else} -

connected: - network: {wallet.walletnetwork} - address: {wallet.walletaddr} +

connected to + network: {wallet.walletnetwork} + with address: {wallet.walletaddr}

{/if}
+


+


+


+


+


+


+





@@ -296,17 +288,18 @@
-

+

- {#if details !== undefined && wallet.walletaddr !== undefined} + {#if details !== undefined && has_wallet}
    +
  • instance address: {@html mk_chainviewer_url(data.addr, wallet.walletnetwork)}
  • daily price: {details.dayPrice / 10**(token_decimals)} {token_symbol}
  • user deposit: {details.deposit / 10**(token_decimals)} {token_symbol}
  • retracted: {details.retracted / 10**(token_decimals)} {token_symbol}
  • -
  • start time: { details.startTime > 0 ? date_formatter.format(details.startTime * 1000) : '' }
  • -
  • end time: {details.endTime > 0 ? date_formatter.format(details.endTime * 1000) : (details.startTime > 0 ? "(" + date_formatter.format(details.startTime * 1000 + (details.deposit * 1000 * 24 * 3600 / details.dayPrice)) + ") estimated" : '') }
  • -
  • provider address: {details.providerAddress}
  • -
  • user address: {details.userAddress}
  • +
  • start time: { details.startTime > 0 ? new Date(details.startTime * 1000).toISOString() : '' }
  • +
  • end time: {details.endTime > 0 ? new Date(details.endTime * 1000).toISOString() : (details.startTime > 0 ? "(" + new Date(details.startTime * 1000 + (details.deposit * 1000 * 24 * 3600 / details.dayPrice)).toISOString() + ") estimated" : '') }
  • +
  • provider address: {@html mk_chainviewer_url(details.providerAddress, wallet.walletnetwork)}
  • +
  • user address: {@html mk_chainviewer_url(details.userAddress, wallet.walletnetwork)}
  • estimated user balance: { calculate_user_balance(details.deposit, details.startTime, details.dayPrice) / 10**(token_decimals)} {token_symbol}
  • estimated provider balance: { calculate_provider_balance(details.deposit, details.retracted, details.startTime, details.dayPrice) / 10**(token_decimals)} {token_symbol}
@@ -360,6 +353,13 @@ {/if}
+


+


+


+


+


+


+





@@ -390,7 +390,7 @@
Timestamp: {#await get_block_time(cev.blockHash) then timestamp} - {date_formatter.format(Number(timestamp * 1000n))} + {new Date(Number(timestamp * 1000n)).toISOString()} {:catch} error {/await} @@ -410,6 +410,13 @@ {/each} {/if} +


+


+


+


+


+


+





@@ -437,6 +444,21 @@


+


+


+


+


+


+


+


+


+


+


+


+


+


+


+ {/if} diff --git a/bca-token-market/src/routes/servicemanager/+page.svelte b/bca-token-market/src/routes/servicemanager/+page.svelte index cc4e1c5..a91d52b 100644 --- a/bca-token-market/src/routes/servicemanager/+page.svelte +++ b/bca-token-market/src/routes/servicemanager/+page.svelte @@ -11,7 +11,7 @@ {#if $page.data.session && is_provider}

Service Manager

-

Service Manager

+

Continue to this system's Service Manager

{/if} \ No newline at end of file diff --git a/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte b/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte index e9af0dc..41e3200 100644 --- a/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte +++ b/bca-token-market/src/routes/servicemanager/[addr]/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte' import { createForm } from "svelte-forms-lib"; import { Contract, Web3 } from 'web3' - import { serviceManagerABI } from "$lib/contracts.js" + import { mk_chainviewer_url, serviceManagerABI, shorten_address } from "$lib/contracts.js" import { WalletInformation, reset_warning, get_wallet_addr, wallet_logout } from '$lib/wallet' export let data @@ -13,11 +13,17 @@ onMount ( async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum); + await local_get_wallet_addr({target: undefined}) + if (wallet.walletaddr && data && data.addr) { + await read_contract() + } } else { wallet.warning = "no web3 wallet attached!" } }) + $: has_wallet = !!wallet.walletaddr + const is_provider = $page.data.session?.user?.role == "Provider" async function local_get_wallet_addr(ev) { @@ -31,10 +37,10 @@ } let details: { ncontrollers: number; providerAddress: string; controllerAddress: string; } | undefined = undefined - async function read_contract(contractAddress: string) { - if (window.web3 && wallet.walletaddr && contractAddress) { + async function read_contract() { + if (window.web3 && has_wallet && data && data.addr) { - let contract: Contract = new window.web3.eth.Contract(serviceManagerABI, contractAddress) + let contract: Contract = new window.web3.eth.Contract(serviceManagerABI, data.addr) const ncontrollers: number = await contract.methods.countServiceControllers().call() let providerAddress: string = "" let controllerAddress: string = "" @@ -49,15 +55,15 @@ } async function create_controller(providerAddress: string, useGas: number) { - if (window.web3 && wallet.walletaddr !== undefined && data !== undefined && data.addr) { + if (window.web3 && has_wallet && data !== undefined && data.addr) { const contract = new window.web3.eth.Contract(serviceManagerABI, data.addr); contract.setConfig({ "defaultNetworkId": wallet.walletnetwork }); try { const gasPrice = await window.web3.eth.getGasPrice(); let estimatedGas = useGas; if (wallet.walletnetwork === "0x89") { // Polygon - let estimatedGas = await contract.methods.newController(providerAddress).estimateGas(); - console.log("estimated gas: " + estimatedGas); + estimatedGas = await contract.methods.newController(providerAddress).estimateGas(); + // console.log("estimated gas: " + estimatedGas); } const receipt = await contract.methods .newController(providerAddress) @@ -77,7 +83,7 @@ const { form, handleChange, handleSubmit } = createForm({ initialValues: { - provider: (wallet.walletaddr !== undefined)?wallet.walletaddr:"address", + provider: has_wallet?wallet.walletaddr:"address", gas: "1450000" }, onSubmit: values => { @@ -92,31 +98,32 @@ {#if $page.data.session && is_provider} -

Service Manager - {data.addr}

+

Service Manager - {shorten_address(data.addr)}

- +

- {#if details !== undefined && wallet.walletaddr !== undefined} + {#if details !== undefined && has_wallet} +

service manager address: {@html mk_chainviewer_url(data.addr, wallet.walletnetwork)}

{#if details.ncontrollers > 0} -

number of controllers: {details.ncontrollers}

-

provider address: {details.providerAddress}

-

controller address: {details.controllerAddress}

-

controller

- {:else} + +

provider address: {@html mk_chainviewer_url(details.providerAddress, wallet.walletnetwork)}

+

provider's controller: {shorten_address(details.controllerAddress)}

+ {:else if is_provider}

Create controller

@@ -140,9 +147,6 @@
{/if} - {/if} {/if}