From 7809a914165368a1d601a5895aa21e7926616f8c Mon Sep 17 00:00:00 2001 From: emochka2007 Date: Tue, 21 May 2024 15:58:49 +0300 Subject: [PATCH] license finished --- .../license/license.controller.ts | 14 ++- .../contract-interact/license/license.dto.ts | 10 +- .../license/license.service.ts | 45 +++++-- .../salaries/salaries.dto.ts | 2 +- .../salaries/salaries.service.ts | 2 +- chain-api/src/hardhat/contracts/Agreement.sol | 118 +++++++++++++++++- ...ense.sol => StreamingRightsManagement.sol} | 35 +++--- chain-api/src/hardhat/test/License.test.ts | 57 +++++++++ .../{Salaries.test.ts => Payroll.test.ts} | 0 9 files changed, 248 insertions(+), 35 deletions(-) rename chain-api/src/hardhat/contracts/{License.sol => StreamingRightsManagement.sol} (86%) create mode 100644 chain-api/src/hardhat/test/License.test.ts rename chain-api/src/hardhat/test/{Salaries.test.ts => Payroll.test.ts} (100%) diff --git a/chain-api/src/contract-interact/license/license.controller.ts b/chain-api/src/contract-interact/license/license.controller.ts index 2c1b84e..6aa6ad0 100644 --- a/chain-api/src/contract-interact/license/license.controller.ts +++ b/chain-api/src/contract-interact/license/license.controller.ts @@ -5,7 +5,9 @@ import { DeployLicenseDto, GetLicenseInfoDto, GetShareLicense, + LicensePayoutDto, RequestLicenseDto, + SetPayoutContractDto, } from './license.dto'; @ApiTags('license') @Controller('license') @@ -37,7 +39,17 @@ export class LicenseController { } @Get('payout-contract') - async getPayoutContract(@Body() dto: GetShareLicense) { + async getPayoutContract(@Body() dto: GetLicenseInfoDto) { return this.licenseService.getPayoutContract(dto); } + + @Post('payout') + async payout(@Body() dto: LicensePayoutDto) { + return this.licenseService.payout(dto); + } + + @Post('set-payout-contract') + async setPayoutContract(@Body() dto: SetPayoutContractDto) { + return this.licenseService.setPayoutContract(dto); + } } diff --git a/chain-api/src/contract-interact/license/license.dto.ts b/chain-api/src/contract-interact/license/license.dto.ts index 72ac255..2f365b2 100644 --- a/chain-api/src/contract-interact/license/license.dto.ts +++ b/chain-api/src/contract-interact/license/license.dto.ts @@ -18,9 +18,6 @@ export class DeployLicenseDto { }) @IsNumber({}, { each: true }) shares: number[]; - @ApiProperty() - @IsString() - payrollAddress: string; } export class RequestLicenseDto extends GetLicenseInfoDto { @@ -36,3 +33,10 @@ export class GetShareLicense extends GetLicenseInfoDto { @ApiProperty() ownerAddress: string; } + +export class LicensePayoutDto extends RequestLicenseDto {} +export class SetPayoutContractDto extends RequestLicenseDto { + @IsString() + @ApiProperty() + payoutContract: string; +} diff --git a/chain-api/src/contract-interact/license/license.service.ts b/chain-api/src/contract-interact/license/license.service.ts index 210ba78..3667e4e 100644 --- a/chain-api/src/contract-interact/license/license.service.ts +++ b/chain-api/src/contract-interact/license/license.service.ts @@ -5,9 +5,10 @@ import { BaseContractService } from '../../base/base-contract.service'; import { DeployLicenseDto, GetLicenseInfoDto, - GetLicenseResponseDto, GetShareLicense, + LicensePayoutDto, RequestLicenseDto, + SetPayoutContractDto, } from './license.dto'; import { MultiSigWalletService } from '../multi-sig/multi-sig.service'; import { ProviderService } from '../../base/provider/provider.service'; @@ -44,18 +45,16 @@ export class LicenseService extends BaseContractService { const contract = new ethers.Contract(contractAddress, abi, signer); - const answer: bigint = await contract.request(); + const answer: bigint = await contract.totalPayoutInUSD(); console.log('=>(license.service.ts:45) answer', answer); return answer.toString(); } async deploy(dto: DeployLicenseDto) { - console.log('=>(license.service.ts:53) dto', dto); - const { multiSigWallet, shares, owners, payrollAddress } = dto; - const { abi, bytecode } = await hre.artifacts.readArtifact( + const { multiSigWallet, shares, owners } = dto; + const { bytecode } = await hre.artifacts.readArtifact( 'StreamingRightsManagement', ); - const signer = await this.providerService.getSigner(); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); @@ -68,7 +67,6 @@ export class LicenseService extends BaseContractService { 'address', 'address[]', 'uint[]', - 'address', ], [ CHAINLINK.AMOY.CHAINLINK_TOKEN, @@ -78,7 +76,6 @@ export class LicenseService extends BaseContractService { multiSigWallet, owners, shares, - payrollAddress, ], ); const fullBytecode = bytecode + abiEncodedConstructorArguments.substring(2); @@ -145,4 +142,36 @@ export class LicenseService extends BaseContractService { return answer; } + + async payout(dto: LicensePayoutDto) { + const { multiSigWallet, contractAddress } = dto; + + const ISubmitMultiSig = new ethers.Interface(['function payout()']); + const data = ISubmitMultiSig.encodeFunctionData('payout'); + + return await this.multiSigService.submitTransaction({ + contractAddress: multiSigWallet, + destination: contractAddress, + value: '0', + data, + }); + } + + async setPayoutContract(dto: SetPayoutContractDto) { + const { multiSigWallet, contractAddress, payoutContract } = dto; + + const ISubmitMultiSig = new ethers.Interface([ + 'function setPayoutContract(address payable)', + ]); + const data = ISubmitMultiSig.encodeFunctionData('setPayoutContract', [ + payoutContract, + ]); + + return await this.multiSigService.submitTransaction({ + contractAddress: multiSigWallet, + destination: contractAddress, + value: '0', + data, + }); + } } diff --git a/chain-api/src/contract-interact/salaries/salaries.dto.ts b/chain-api/src/contract-interact/salaries/salaries.dto.ts index 5cee16d..f42a973 100644 --- a/chain-api/src/contract-interact/salaries/salaries.dto.ts +++ b/chain-api/src/contract-interact/salaries/salaries.dto.ts @@ -4,7 +4,7 @@ import { IsNumber, IsString } from 'class-validator'; export class SalariesDeployDto { @ApiProperty() @IsString() - multiSigWallet: string; + authorizedWallet: string; } export class SetSalaryDto { diff --git a/chain-api/src/contract-interact/salaries/salaries.service.ts b/chain-api/src/contract-interact/salaries/salaries.service.ts index 1e1d459..394e3dc 100644 --- a/chain-api/src/contract-interact/salaries/salaries.service.ts +++ b/chain-api/src/contract-interact/salaries/salaries.service.ts @@ -29,7 +29,7 @@ export class SalariesService extends BaseContractService { const salaryContract = new ethers.ContractFactory(abi, bytecode, signer); const myContract = await salaryContract.deploy( - dto.multiSigWallet, + dto.authorizedWallet, CHAINLINK.AMOY.AGGREGATOR_ADDRESS.USDT_ETH, ); await myContract.waitForDeployment(); diff --git a/chain-api/src/hardhat/contracts/Agreement.sol b/chain-api/src/hardhat/contracts/Agreement.sol index 026e656..f24987a 100644 --- a/chain-api/src/hardhat/contracts/Agreement.sol +++ b/chain-api/src/hardhat/contracts/Agreement.sol @@ -1,10 +1,118 @@ -// +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; +import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"; /** + * Request testnet LINK and ETH here: https://faucets.chain.link/ + * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/ + */ -License -sender -receiver +/** + * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. + */ +contract LinkWellBoolConsumerContractExample is ChainlinkClient, ConfirmedOwner { + using Chainlink for Chainlink.Request; - */ \ No newline at end of file + address private oracleAddress; + bytes32 private jobId; + uint256 private fee; + + constructor( + address _chainLinkToken, + address _oracleAddress, + string memory _jobId, + uint _fee, + address _multiSigAddress, + address[] memory _owners, + uint[] memory _shares + ) ConfirmedOwner(_multiSigAddress) { + + _setChainlinkToken(_chainLinkToken); + + setOracleAddress(_oracleAddress); + + setJobId(_jobId); + + setFeeInHundredthsOfLink(_fee); + + multisigWallet = _multiSigAddress; + } + + constructor() ConfirmedOwner(msg.sender) { + _setChainlinkToken(0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904); + setOracleAddress(0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46); + setJobId("43309009a154495cb2ed794233e6ff56"); + setFeeInHundredthsOfLink(0); // 0 LINK + } + + // Send a request to the Chainlink oracle + function request() public { + + Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector); + + // DEFINE THE REQUEST PARAMETERS (example) + req._add('method', 'POST'); + req._add('url', 'https://httpbin.org/post'); + req._add('headers', '["accept", "application/json", "set-cookie", "sid=14A52"]'); + req._add('body', '{"data":[{"coin":"BTC","isActive":false},{"coin":"ETH","isActive":false},{"coin":"LINK","isActive":true}]}'); + req._add('contact', ''); // PLEASE ENTER YOUR CONTACT INFO. this allows us to notify you in the event of any emergencies related to your request (ie, bugs, downtime, etc.). example values: 'derek_linkwellnodes.io' (Discord handle) OR 'derek@linkwellnodes.io' OR '+1-617-545-4721' + + // The following curl command simulates the above request parameters: + // curl 'https://httpbin.org/post' --request 'POST' --header 'content-type: application/json' --header 'set-cookie: sid=14A52' --data '{"data":[{"coin":"BTC","isActive":false},{"coin":"ETH","isActive":false},{"coin":"LINK","isActive":true}]}' + + // PROCESS THE RESULT (example) + req._add('path', 'json,data,2,isActive'); + + // Send the request to the Chainlink oracle + _sendOperatorRequest(req, fee); + } + + bool public response; + + // Receive the result from the Chainlink oracle + event RequestFulfilled(bytes32 indexed requestId); + function fulfill(bytes32 requestId, bool data) public recordChainlinkFulfillment(requestId) { + // Process the oracle response + // emit RequestFulfilled(requestId); // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction + response = data; // example value: true + } + + // Update oracle address + function setOracleAddress(address _oracleAddress) public onlyOwner { + oracleAddress = _oracleAddress; + _setChainlinkOracle(_oracleAddress); + } + function getOracleAddress() public view onlyOwner returns (address) { + return oracleAddress; + } + + // Update jobId + function setJobId(string memory _jobId) public onlyOwner { + jobId = bytes32(bytes(_jobId)); + } + function getJobId() public view onlyOwner returns (string memory) { + return string(abi.encodePacked(jobId)); + } + + // Update fees + function setFeeInJuels(uint256 _feeInJuels) public onlyOwner { + fee = _feeInJuels; + } + function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner { + setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100); + } + function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) { + return (fee * 100) / LINK_DIVISIBILITY; + } + + function withdrawLink() public onlyOwner { + LinkTokenInterface link = LinkTokenInterface(_chainlinkTokenAddress()); + require( + link.transfer(msg.sender, link.balanceOf(address(this))), + "Unable to transfer" + ); + } +} \ No newline at end of file diff --git a/chain-api/src/hardhat/contracts/License.sol b/chain-api/src/hardhat/contracts/StreamingRightsManagement.sol similarity index 86% rename from chain-api/src/hardhat/contracts/License.sol rename to chain-api/src/hardhat/contracts/StreamingRightsManagement.sol index 1cff4ad..37eb5a1 100644 --- a/chain-api/src/hardhat/contracts/License.sol +++ b/chain-api/src/hardhat/contracts/StreamingRightsManagement.sol @@ -4,14 +4,7 @@ pragma solidity ^0.8.17; import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"; import "./Payroll.sol"; -/** - * Request testnet LINK and ETH here: https://faucets.chain.link/ - * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/ - */ -/** - * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. - */ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { using Chainlink for Chainlink.Request; @@ -33,8 +26,7 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { uint _fee, address _multiSigAddress, address[] memory _owners, - uint[] memory _shares, - address payable _payoutAddress + uint[] memory _shares ) ConfirmedOwner(_multiSigAddress) { _setChainlinkToken(_chainLinkToken); @@ -47,7 +39,6 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { multisigWallet = _multiSigAddress; - payoutContract = Payroll(_payoutAddress); require(_owners.length == _shares.length, "Owners and shares length mismatch"); @@ -68,10 +59,20 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { //update share //change payout address // + modifier hasValidPayoutContract() { + require(address(payoutContract) != address(0), "payoutContract not initialized"); + _; + } + function getShare(address owner) public view returns(uint){ return ownerShare[owner]; } + function setPayoutContract(address payable _payoutAddress) public onlyOwner { + require(_payoutAddress != address(0), "Invalid address: zero address not allowed"); + payoutContract = Payroll(_payoutAddress); + } + // Send a request to the Chainlink oracle function request() external onlyOwner{ @@ -90,6 +91,7 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { // PROCESS THE RESULT (example) req._add('path', 'ETH,USD'); + req._addInt('multiplier', 10 ** 18); // Send the request to the Chainlink oracle _sendOperatorRequest(req, fee); } @@ -102,15 +104,16 @@ contract StreamingRightsManagement is ChainlinkClient, ConfirmedOwner { function fulfill(bytes32 requestId, uint256 data) public recordChainlinkFulfillment(requestId) { // Process the oracle response // emit RequestFulfilled(requestId); // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction - totalPayoutInUSD = data / 100; // example value: 1875870000000000000000 (1875.87 before "multiplier" is applied) + totalPayoutInUSD = data / 1e18 / 100; // example value: 1875870000000000000000 (1875.87 before "multiplier" is applied) } - function payout() external onlyOwner { - // using arrays to reduce gas - uint[] memory shares; + function payout() external onlyOwner hasValidPayoutContract{ - for(uint i=0; i< owners.length; i++){ - shares[i] = ownerShare[owners[i]]; + // using arrays to reduce gas + uint[] memory shares = new uint[](owners.length); + + for(uint i=0; i< owners.length; i++){ + shares[i] = ownerShare[owners[i]] * totalPayoutInUSD / 100; } payoutContract.oneTimePayout(owners, shares); } diff --git a/chain-api/src/hardhat/test/License.test.ts b/chain-api/src/hardhat/test/License.test.ts new file mode 100644 index 0000000..3d0d0af --- /dev/null +++ b/chain-api/src/hardhat/test/License.test.ts @@ -0,0 +1,57 @@ +import { StreamingRightsManagement } from '../../../typechain'; +import { CHAINLINK } from '../../config/chainlink.config'; + +const { expect } = require('chai'); +const { ethers } = require('hardhat'); + +describe('StreamingRightsManagement', function () { + let streamingRightsManagement: StreamingRightsManagement, + payContract, + owner, + addr1, + addr2; + const shares = [25, 25, 50]; + + beforeEach(async function () { + [owner, addr1, addr2] = await ethers.getSigners(); + + const Payroll = await ethers.getContractFactory('Payroll'); + payContract = await Payroll.deploy(owner.address, owner.address); // assume an oracle price feed address + + const StreamingRightsManagement = await ethers.getContractFactory( + 'StreamingRightsManagement', + ); + streamingRightsManagement = await StreamingRightsManagement.deploy( + CHAINLINK.AMOY.CHAINLINK_TOKEN, // Chainlink Token address + CHAINLINK.AMOY.ORACLE_ADDRESS, // Oracle address + CHAINLINK.AMOY.JOB_IDS.UINT, + 0, + owner.address, + [owner.address, addr1.address, addr2.address], + shares, + ); + }); + + describe('Initialization', function () { + it('should set owners and shares correctly', async function () { + expect(await streamingRightsManagement.getShare(owner.address)).to.equal( + 25, + ); + expect(await streamingRightsManagement.getShare(addr1.address)).to.equal( + 25, + ); + expect(await streamingRightsManagement.getShare(addr2.address)).to.equal( + 50, + ); + }); + }); + + describe('Payout Functionality', function () { + it('should successfully call payout', async function () { + await streamingRightsManagement.setPayoutContract(payContract.address); + await expect(streamingRightsManagement.payout()).to.not.be.reverted; + }); + }); + + // More tests as needed for other functions +}); diff --git a/chain-api/src/hardhat/test/Salaries.test.ts b/chain-api/src/hardhat/test/Payroll.test.ts similarity index 100% rename from chain-api/src/hardhat/test/Salaries.test.ts rename to chain-api/src/hardhat/test/Payroll.test.ts