From 59d09720a24df4f3b6241cf01aa5b0599f400148 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Wed, 8 Nov 2023 13:39:10 +0000 Subject: [PATCH 01/10] Update AutonomousSoftwareOrg smart contract --- .gitignore | 1 + AutonomousSoftwareOrg.sol | 356 ---------------------------- brownie-config.yaml | 5 + contracts/AutonomousSoftwareOrg.sol | 348 +++++++++++++++++++++++++++ contracts/SafeMath.sol | 29 +++ contracts/Token.sol | 125 ++++++++++ requirements.txt | 1 + run.sh | 14 ++ scripts/token.py | 7 + tests/_test.py | 205 ++++++++++++++++ tests/conftest.py | 23 ++ tests/test_approve.py | 53 +++++ tests/test_transfer.py | 16 ++ tests/test_transferFrom.py | 164 +++++++++++++ token/.gitattributes | 1 + token/.github/workflows/main.yaml | 42 ++++ token/.gitignore | 5 + 17 files changed, 1039 insertions(+), 356 deletions(-) create mode 100644 .gitignore delete mode 100644 AutonomousSoftwareOrg.sol create mode 100644 brownie-config.yaml create mode 100644 contracts/AutonomousSoftwareOrg.sol create mode 100644 contracts/SafeMath.sol create mode 100644 contracts/Token.sol create mode 100644 requirements.txt create mode 100755 run.sh create mode 100644 scripts/token.py create mode 100644 tests/_test.py create mode 100644 tests/conftest.py create mode 100644 tests/test_approve.py create mode 100644 tests/test_transfer.py create mode 100644 tests/test_transferFrom.py create mode 100644 token/.gitattributes create mode 100644 token/.github/workflows/main.yaml create mode 100644 token/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..566be7a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +reports \ No newline at end of file diff --git a/AutonomousSoftwareOrg.sol b/AutonomousSoftwareOrg.sol deleted file mode 100644 index fb1c0a0..0000000 --- a/AutonomousSoftwareOrg.sol +++ /dev/null @@ -1,356 +0,0 @@ -pragma solidity ^0.4.10; - -contract AutonomousSoftwareOrg { - - struct SoftwareVersionRecord { - address submitter; - bytes32 url; - bytes32 version; - uint256 sourcehash; - } - - struct SoftwareExecRecord { - address submitter; - bytes32 softwareversion; - bytes32 url; - uint256 inputhash; - uint256 outputhash; - } - - struct MemberInfo { - bytes32 url; - address memberaddr; - uint votecount; - mapping(address => bool) voted; - } - - struct Proposal { - bytes32 title; - bytes32 url; - uint256 prophash; - address proposer; - uint requestedfund; - uint deadline; - uint votecount; - bool withdrawn; - mapping(address => bool) voted; - } - - struct Donation { - address donor; - uint amnt; - uint blkno; - } - - bytes32 public softwarename; - - uint public balance; - uint public nummembers; - - uint8 public M; - uint8 public N; - - mapping(address => uint) public members; - mapping(address => bool) public membersTryOut; - - SoftwareVersionRecord[] versions; - SoftwareExecRecord[] execrecords; - - MemberInfo[] membersinfo; - Proposal[] proposals; - Donation[] donations; - bytes32[] citations; - address[] usedbysoftware; - - event LogSoftwareExecRecord(address submitter,bytes32 softwareversion,bytes32 url,uint256 inputhash,uint256 outputhash); - event LogSoftwareVersionRecord(address submitter,bytes32 url,bytes32 version,uint256 sourcehash); - event LogPropose(uint propNo,bytes32 title,bytes32 ipfsHash,uint requestedFund,uint deadline); - event LogProposalVote(uint votecount,uint blocknum,address voter); - event LogDonation(address donor,uint amount,uint blknum); - event LogWithdrawProposalFund(uint propno,uint requestedfund,uint blocknum,address proposalOwner); - event LogVoteMemberCandidate(uint memberno,address voter,uint votecount); - - modifier enough_fund_balance(uint propno) { - require(balance >= proposals[propno].requestedfund ); - _; - } - - modifier valid_proposal_no(uint propno) { - require(propno < proposals.length ); - _; - } - - modifier valid_member_no(uint memberno) { - require((memberno!=0) && (memberno <= membersinfo.length)); - _; - } - - modifier member(address addr) { - require( members[addr] != 0 ); - _; - } - - modifier not_member(address addr) { - require( members[addr] == 0 ); - _; - } - - modifier valid_deadline(uint deadline) { - require(deadline >= block.number ); - _; - } - - modifier within_deadline(uint propno) { - require( proposals[propno].deadline > block.number ); - _; - } - - modifier not_voted_for_proposal(uint propno) { - require(! proposals[propno].voted[msg.sender] ); - _; - } - - modifier not_voted_for_member(uint memberno) { - require(! membersinfo[memberno-1].voted[msg.sender] ); - _; - } - - modifier voted_for_member(uint memberno) { - require(membersinfo[memberno-1].voted[msg.sender] ); - _; - } - - modifier proposal_owner(uint propno) { - require(proposals[propno].proposer == msg.sender ); - _; - } - - - modifier proposal_majority(uint propno) { - require((proposals[propno].votecount*N) >= (nummembers*M)); - _; - } - - modifier membership_majority(uint memberno) { - require((membersinfo[memberno].votecount*N) >= (nummembers*M)); - _; - } - - modifier nonzero_payment_made() { - require(msg.value > 0); - _; - } - - function AutonomousSoftwareOrg(bytes32 name,uint8 m,uint8 n, bytes32 url) { - if( m > n ) - throw; - - softwarename = name; - membersinfo.push(MemberInfo(url,msg.sender,0)); - members[msg.sender] = membersinfo.length; - balance = 0; - nummembers = 1; - M = m; - N = n; - } - - function () { - throw; - } - - function ProposeProposal(bytes32 title,bytes32 url,uint256 prophash,uint requestedfund, uint deadline) public - member(msg.sender) valid_deadline(deadline) { - proposals.push(Proposal(title,url,prophash,msg.sender,requestedfund,deadline,0,false)); - LogPropose( proposals.length, title, url, requestedfund, deadline ); - - } - - function VoteForProposal(uint propno) public - valid_proposal_no(propno) within_deadline(propno) - member(msg.sender) not_voted_for_proposal(propno) { - proposals[propno].voted[msg.sender] = true; - proposals[propno].votecount++; - LogProposalVote(proposals[propno].votecount,block.number,msg.sender); - } - - function WithdrawProposalFund(uint propno) public - valid_proposal_no(propno) within_deadline(propno) - member(msg.sender) enough_fund_balance(propno) proposal_owner(propno) - proposal_majority(propno) { - balance -= proposals[propno].requestedfund; - if (proposals[propno].withdrawn == true || ! msg.sender.send(proposals[propno].requestedfund)) { - throw; - } - proposals[propno].withdrawn = true; - LogWithdrawProposalFund(propno,proposals[propno].requestedfund,block.number,msg.sender); - } - - function BecomeMemberCandidate(bytes32 url) public - not_member(msg.sender) { - if(membersTryOut[msg.sender] == true) - throw; - - membersinfo.push(MemberInfo(url,msg.sender,0)); - membersTryOut[msg.sender] = true; - } - - - function VoteMemberCandidate(uint memberno) public valid_member_no(memberno) - member(msg.sender) not_voted_for_member(memberno) { - membersinfo[memberno-1].voted[msg.sender] = true; - membersinfo[memberno-1].votecount++; - - if ((membersinfo[memberno-1].votecount)*N >= (nummembers*M)) { - if (members[membersinfo[memberno-1].memberaddr] == 0) { - members[membersinfo[memberno-1].memberaddr] = memberno; - nummembers++; - } - } - LogVoteMemberCandidate(memberno-1,msg.sender,membersinfo[memberno-1].votecount); - } - - function DelVoteMemberCandidate(uint memberno) public - valid_member_no(memberno) member(msg.sender) voted_for_member(memberno) { - membersinfo[memberno-1].voted[msg.sender] = false; - membersinfo[memberno-1].votecount--; - - if ((membersinfo[memberno-1].votecount * N) < (nummembers*M)) { - if (members[membersinfo[memberno-1].memberaddr] != 0) { - delete(members[membersinfo[memberno-1].memberaddr]); - nummembers--; - } - } - } - - function Donate() payable public - nonzero_payment_made { - balance += msg.value; - donations.push(Donation(msg.sender,msg.value,block.number)); - LogDonation(msg.sender, msg.value, block.number); - - } - - function Cite(bytes32 doinumber) public { - citations.push(doinumber); - } - - function UseBySoftware(address addr) public { - usedbysoftware.push(addr); - } - - function addSoftwareExecRecord(bytes32 softwareversion,bytes32 url,uint256 inputhash,uint256 outputhash) - member(msg.sender) { - execrecords.push(SoftwareExecRecord(msg.sender,softwareversion,url,inputhash,outputhash)); - LogSoftwareExecRecord(msg.sender,softwareversion,url,inputhash,outputhash); - } - - function addSoftwareVersionRecord(bytes32 url,bytes32 version,uint256 sourcehash) { - versions.push(SoftwareVersionRecord(msg.sender,url,version,sourcehash)); - LogSoftwareVersionRecord(msg.sender,url,version,sourcehash); - } - - function getSoftwareExecRecord(uint32 id) - constant returns(address,bytes32,bytes32,uint256,uint256) { - return(execrecords[id].submitter, - execrecords[id].softwareversion, - execrecords[id].url, - execrecords[id].inputhash, - execrecords[id].outputhash); - } - - function getSoftwareExecRecordLength() - constant returns (uint) { - return(execrecords.length); - } - - function getSoftwareVersionRecords(uint32 id) - constant returns(address,bytes32,bytes32,uint256) { - return(versions[id].submitter, - versions[id].url, - versions[id].version, - versions[id].sourcehash); - } - - function geSoftwareVersionRecordsLength() - constant returns (uint) { - return(versions.length); - } - - function getAutonomousSoftwareOrgInfo() - constant returns (bytes32,uint,uint,uint,uint) { - return (softwarename, - balance, - nummembers, - M, - N ); - } - - function getMemberInfoLength() - constant returns (uint) { - return(membersinfo.length); - } - - function getMemberInfo(uint memberno) - member(membersinfo[memberno-1].memberaddr) - constant returns (bytes32,address,uint ) { - return (membersinfo[memberno-1].url, - membersinfo[memberno-1].memberaddr, - membersinfo[memberno-1].votecount); - } - - function getCandidateMemberInfo(uint memberno) - not_member(membersinfo[memberno-1].memberaddr) - constant returns (bytes32,address,uint ) { - return (membersinfo[memberno-1].url, - membersinfo[memberno-1].memberaddr, - membersinfo[memberno-1].votecount); - } - - function getProposalsLength() - constant returns (uint) { - return(proposals.length); - } - - function getProposal(uint propno) - constant returns (bytes32,bytes32,uint256,uint,uint,bool,uint) { - return (proposals[propno].title, - proposals[propno].url, - proposals[propno].prophash, - proposals[propno].requestedfund, - proposals[propno].deadline, - proposals[propno].withdrawn, - proposals[propno].votecount); - } - - function getDonationLength() - constant returns (uint) { - return (donations.length); - } - - function getDonationInfo(uint donationno) - constant returns (address,uint,uint) { - return (donations[donationno].donor, - donations[donationno].amnt, - donations[donationno].blkno); - } - - function getCitationLength() - constant returns (uint) { - return (citations.length); - } - - function getCitation(uint citeno) - constant returns (bytes32) { - return (citations[citeno]); - } - - function getUsedBySoftwareLength() - constant returns (uint) { - return (usedbysoftware.length); - } - - function getUsedBySoftware(uint usedbysoftwareno) - constant returns (address) { - return (usedbysoftware[usedbysoftwareno]); - } -} diff --git a/brownie-config.yaml b/brownie-config.yaml new file mode 100644 index 0000000..049ef8e --- /dev/null +++ b/brownie-config.yaml @@ -0,0 +1,5 @@ +# exclude SafeMath when calculating test coverage +# https://eth-brownie.readthedocs.io/en/v1.10.3/config.html#exclude_paths +reports: + exclude_contracts: + - SafeMath diff --git a/contracts/AutonomousSoftwareOrg.sol b/contracts/AutonomousSoftwareOrg.sol new file mode 100644 index 0000000..16da1f2 --- /dev/null +++ b/contracts/AutonomousSoftwareOrg.sol @@ -0,0 +1,348 @@ +pragma solidity ^0.6.0; + +// SPDX-License-Identifier: MIT + +contract AutonomousSoftwareOrg { + + struct SoftwareVersionRecord { + address submitter; + bytes32 url; + bytes32 version; + uint256 sourceCodeHash; + } + + struct SoftwareExecRecord { + address submitter; + bytes32 softwareVersion; + bytes32 url; + uint256 inputHash; + uint256 outputHash; + } + + struct MemberInfo { + bytes32 url; + address memberAddr; + uint voteCount; + mapping(address => bool) voted; + } + + struct Proposal { + bytes32 title; + bytes32 url; + uint256 propHash; + address proposer; + uint requestedFund; + uint deadline; + uint voteCount; + bool withdrawn; + mapping(address => bool) voted; + } + + struct Donation { + address donor; + uint amnt; + uint blkno; + } + + bytes32 public softwareName; + + uint public balance; + uint public numMembers; + + uint8 public M; + uint8 public N; + + mapping(address => uint) public members; + mapping(address => bool) public membersTryOut; + + SoftwareVersionRecord[] versions; + SoftwareExecRecord[] execRecords; + + MemberInfo[] membersInfo; + Proposal[] proposals; + Donation[] donations; + bytes32[] citations; + address[] usedBySoftware; + + event LogSoftwareExecRecord(address submitter, bytes32 softwareVersion, bytes32 url, uint256 inputHash, uint256 outputHash); + event LogSoftwareVersionRecord(address submitter, bytes32 url, bytes32 version, uint256 sourceCodeHash); + event LogPropose(uint propNo,bytes32 title, bytes32 ipfsHash, uint requestedFund, uint deadline); + event LogProposalVote(uint voteCount, uint blockNum, address voter); + event LogDonation(address donor,uint amount,uint blknum); + event LogWithdrawProposalFund(uint propNo, uint requestedFund, uint blockNum, address proposalOwner); + event LogVoteMemberCandidate(uint memberNo,address voter,uint voteCount); + + modifier enough_fund_balance(uint propNo) { + require(balance >= proposals[propNo].requestedFund); + _; + } + + modifier validProposalNo(uint propNo) { + require(propNo < proposals.length); + _; + } + + modifier validMemberNo(uint memberNo) { + require((memberNo!=0) && (memberNo <= membersInfo.length)); + _; + } + + modifier member(address addr) { + require( members[addr] != 0); + _; + } + + modifier notMember(address addr) { + require( members[addr] == 0); + _; + } + + modifier validDeadline(uint deadline) { + require(deadline >= block.number); + _; + } + + modifier withinDeadline(uint propNo) { + require( proposals[propNo].deadline > block.number); + _; + } + + modifier notVotedForProposal(uint propNo) { + require(! proposals[propNo].voted[msg.sender]); + _; + } + + modifier notVotedForMember(uint memberNo) { + require(! membersInfo[memberNo-1].voted[msg.sender]); + _; + } + + modifier votedForMember(uint memberNo) { + require(membersInfo[memberNo-1].voted[msg.sender]); + _; + } + + modifier proposalOwner(uint propNo) { + require(proposals[propNo].proposer == msg.sender); + _; + } + + + modifier proposalMajority(uint propNo) { + require((proposals[propNo].voteCount*N) >= (numMembers * M)); + _; + } + + modifier membershipMajority(uint memberNo) { + require((membersInfo[memberNo].voteCount*N) >= (numMembers * M)); + _; + } + + modifier nonzeroPaymentMade() { + require(msg.value > 0); + _; + } + + constructor(bytes32 name,uint8 m,uint8 n, bytes32 url) public { + if (m > n) + revert(); + + softwareName = name; + membersInfo.push(MemberInfo(url, msg.sender, 0)); + members[msg.sender] = membersInfo.length; + balance = 0; + numMembers = 1; + M = m; + N = n; + } + + function ProposeProposal(bytes32 title, bytes32 url, uint256 propHash, uint requestedFund, uint deadline) public + member(msg.sender) validDeadline(deadline) { + proposals.push(Proposal(title,url, propHash, msg.sender, requestedFund, deadline, 0, false)); + emit LogPropose( proposals.length, title, url, requestedFund, deadline); + } + + function VoteForProposal(uint propNo) public + validProposalNo(propNo) withinDeadline(propNo) + member(msg.sender) notVotedForProposal(propNo) { + proposals[propNo].voted[msg.sender] = true; + proposals[propNo].voteCount++; + emit LogProposalVote(proposals[propNo].voteCount,block.number,msg.sender); + } + + function WithdrawProposalFund(uint propNo) public + validProposalNo(propNo) withinDeadline(propNo) + member(msg.sender) enough_fund_balance(propNo) proposalOwner(propNo) + proposalMajority(propNo) { + balance -= proposals[propNo].requestedFund; + if (proposals[propNo].withdrawn == true || ! msg.sender.send(proposals[propNo].requestedFund)) { + revert(); + } + proposals[propNo].withdrawn = true; + emit LogWithdrawProposalFund(propNo,proposals[propNo].requestedFund,block.number,msg.sender); + } + + function BecomeMemberCandidate(bytes32 url) public + notMember(msg.sender) { + if(membersTryOut[msg.sender] == true) + revert(); + + membersInfo.push(MemberInfo(url, msg.sender, 0)); + membersTryOut[msg.sender] = true; + } + + + function VoteMemberCandidate(uint memberNo) public validMemberNo(memberNo) + member(msg.sender) notVotedForMember(memberNo) { + membersInfo[memberNo-1].voted[msg.sender] = true; + membersInfo[memberNo-1].voteCount++; + + if ((membersInfo[memberNo - 1].voteCount) * N >= (numMembers * M)) { + if (members[membersInfo[memberNo - 1].memberAddr] == 0) { + members[membersInfo[memberNo - 1].memberAddr] = memberNo; + numMembers++; + } + } + emit LogVoteMemberCandidate(memberNo - 1, msg.sender, membersInfo[memberNo - 1].voteCount); + } + + function DelVoteMemberCandidate(uint memberNo) public + validMemberNo(memberNo) member(msg.sender) votedForMember(memberNo) { + membersInfo[memberNo-1].voted[msg.sender] = false; + membersInfo[memberNo-1].voteCount--; + if ((membersInfo[memberNo-1].voteCount * N) < (numMembers*M)) { + if (members[membersInfo[memberNo-1].memberAddr] != 0) { + delete(members[membersInfo[memberNo-1].memberAddr]); + numMembers--; + } + } + } + + function Donate() payable public + nonzeroPaymentMade { + balance += msg.value; + donations.push(Donation(msg.sender,msg.value,block.number)); + emit LogDonation(msg.sender, msg.value, block.number); + + } + + function Cite(bytes32 doiNumber) public { + citations.push(doiNumber); + } + + function UseBySoftware(address addr) public { + usedBySoftware.push(addr); + } + + function addSoftwareExecRecord(bytes32 softwareVersion, bytes32 url, uint256 inputHash,uint256 outputHash) + public member(msg.sender) { + execRecords.push(SoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash)); + emit LogSoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash); + } + + function addSoftwareVersionRecord(bytes32 url, bytes32 version, uint256 sourceCodeHash) public { + versions.push(SoftwareVersionRecord(msg.sender, url, version, sourceCodeHash)); + emit LogSoftwareVersionRecord(msg.sender, url, version, sourceCodeHash); + } + + function getSoftwareExecRecord(uint32 id) + public view returns(address,bytes32,bytes32,uint256,uint256) { + return(execRecords[id].submitter, + execRecords[id].softwareVersion, + execRecords[id].url, + execRecords[id].inputHash, + execRecords[id].outputHash); + } + + function getSoftwareExecRecordLength() + public view returns (uint) { + return(execRecords.length); + } + + function getSoftwareVersionRecords(uint32 id) + public view returns(address,bytes32,bytes32,uint256) { + return(versions[id].submitter, + versions[id].url, + versions[id].version, + versions[id].sourceCodeHash); + } + + function geSoftwareVersionRecordsLength() + public view returns (uint) { + return(versions.length); + } + + function getAutonomousSoftwareOrgInfo() + public view returns (bytes32,uint,uint,uint,uint) { + return (softwareName, balance, numMembers, M, N); + } + + function getMemberInfoLength() + public view returns (uint) { + return(membersInfo.length); + } + + function getMemberInfo(uint memberNo) + member(membersInfo[memberNo-1].memberAddr) + public view returns (bytes32,address, uint) { + return (membersInfo[memberNo-1].url, + membersInfo[memberNo-1].memberAddr, + membersInfo[memberNo-1].voteCount); + } + + function getCandidateMemberInfo(uint memberNo) + notMember(membersInfo[memberNo-1].memberAddr) + public view returns (bytes32,address, uint) { + return (membersInfo[memberNo-1].url, + membersInfo[memberNo-1].memberAddr, + membersInfo[memberNo-1].voteCount); + } + + function getProposalsLength() + public view returns (uint) { + return(proposals.length); + } + + function getProposal(uint propNo) + public view returns (bytes32, bytes32, uint256, uint, uint, bool, uint) { + return (proposals[propNo].title, + proposals[propNo].url, + proposals[propNo].propHash, + proposals[propNo].requestedFund, + proposals[propNo].deadline, + proposals[propNo].withdrawn, + proposals[propNo].voteCount); + } + + function getDonationLength() + public view returns (uint) { + return (donations.length); + } + + function getDonationInfo(uint donationNo) + public view returns (address,uint,uint) { + return (donations[donationNo].donor, + donations[donationNo].amnt, + donations[donationNo].blkno); + } + + function getCitationLength() + public view returns (uint) { + return (citations.length); + } + + function getCitation(uint citeno) + public view returns (bytes32) { + return (citations[citeno]); + } + + function getUsedBySoftwareLength() + public view returns (uint) { + return (usedBySoftware.length); + } + + function getUsedBySoftware(uint usedBySoftwareNo) + public view returns (address) { + return (usedBySoftware[usedBySoftwareNo]); + } +} diff --git a/contracts/SafeMath.sol b/contracts/SafeMath.sol new file mode 100644 index 0000000..38ca609 --- /dev/null +++ b/contracts/SafeMath.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.6.0; + +library SafeMath { + + function add(uint a, uint b) internal pure returns (uint c) { + c = a + b; + require(c >= a); + return c; + } + + function sub(uint a, uint b) internal pure returns (uint c) { + require(b <= a); + c = a - b; + return c; + } + + function mul(uint a, uint b) internal pure returns (uint c) { + c = a * b; + require(a == 0 || c / a == b); + return c; + } + + function div(uint a, uint b) internal pure returns (uint c) { + require(b > 0); + c = a / b; + return c; + } + +} diff --git a/contracts/Token.sol b/contracts/Token.sol new file mode 100644 index 0000000..a92ebc4 --- /dev/null +++ b/contracts/Token.sol @@ -0,0 +1,125 @@ +pragma solidity ^0.6.0; + +// SPDX-License-Identifier: MIT + +import "./SafeMath.sol"; + +/** + @title Bare-bones Token implementation + @notice Based on the ERC-20 token standard as defined at + https://eips.ethereum.org/EIPS/eip-20 + */ +contract Token { + + using SafeMath for uint256; + + string public symbol; + string public name; + uint256 public decimals; + uint256 public totalSupply; + + mapping(address => uint256) balances; + mapping(address => mapping(address => uint256)) allowed; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + constructor( + string memory _name, + string memory _symbol, + uint256 _decimals, + uint256 _totalSupply + ) + public + { + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = _totalSupply; + balances[msg.sender] = _totalSupply; + emit Transfer(address(0), msg.sender, _totalSupply); + } + + /** + @notice Getter to check the current balance of an address + @param _owner Address to query the balance of + @return Token balance + */ + function balanceOf(address _owner) public view returns (uint256) { + return balances[_owner]; + } + + /** + @notice Getter to check the amount of tokens that an owner allowed to a spender + @param _owner The address which owns the funds + @param _spender The address which will spend the funds + @return The amount of tokens still available for the spender + */ + function allowance( + address _owner, + address _spender + ) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + /** + @notice Approve an address to spend the specified amount of tokens on behalf of msg.sender + @dev Beware that changing an allowance with this method brings the risk that someone may use both the old + and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will spend the funds. + @param _value The amount of tokens to be spent. + @return Success boolean + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** shared logic for transfer and transferFrom */ + function _transfer(address _from, address _to, uint256 _value) internal { + require(balances[_from] >= _value, "Insufficient balance"); + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(_from, _to, _value); + } + + /** + @notice Transfer tokens to a specified address + @param _to The address to transfer to + @param _value The amount to be transferred + @return Success boolean + */ + function transfer(address _to, uint256 _value) public returns (bool) { + _transfer(msg.sender, _to, _value); + return true; + } + + /** + @notice Transfer tokens from one address to another + @param _from The address which you want to send tokens from + @param _to The address which you want to transfer to + @param _value The amount of tokens to be transferred + @return Success boolean + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) + public + returns (bool) + { + require(allowed[_from][msg.sender] >= _value, "Insufficient allowance"); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + _transfer(_from, _to, _value); + return true; + } + +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e4955b8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +eth-brownie>=1.10.0,<2.0.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..1deeeea --- /dev/null +++ b/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +TEST_ALL=0 +source ~/venv/bin/activate +echo -n "brownie compile " +brownie compile >/dev/null 2>&1 +echo "done" +$HOME/ebloc-broker/broker/_daemons/ganache.py 8547 +if [ $TEST_ALL -eq 1 ]; then + pytest tests -s -x --disable-pytest-warnings --log-level=INFO -v --tb=line # tests all cases +else #: gives priority + pytest tests --capture=sys -s -x -k "test_AutonomousSoftwareOrg" --disable-pytest-warnings -vv --tb=line +fi +rm -rf reports/ diff --git a/scripts/token.py b/scripts/token.py new file mode 100644 index 0000000..df8978d --- /dev/null +++ b/scripts/token.py @@ -0,0 +1,7 @@ +#!/usr/bin/python3 + +from brownie import Token, accounts + + +def main(): + return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]}) diff --git a/tests/_test.py b/tests/_test.py new file mode 100644 index 0000000..6c5e182 --- /dev/null +++ b/tests/_test.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 + + +def _test_AutonomousSoftwareOrg(web3, accounts, chain): + print(accounts) + autonomousSoftwareOrg, _ = chain.provider.get_or_deploy_contract( + "AutonomousSoftwareOrg" + ) + + tx = autonomousSoftwareOrg.transact().AutonomousSoftwareOrg("alper", 2, 3, "0x") + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[0] + ( + softwarename, + balance, + nummembers, + M, + N, + ) = autonomousSoftwareOrg.call().getAutonomousSoftwareOrgInfo() + print( + "name: " + + softwarename + + " |balance: " + + str(balance) + + " |numMembers: " + + str(nummembers) + + " |M: " + + str(M) + + " |N:" + + str(N) + ) + + web3.eth.defaultAccount = accounts[1] + tx = autonomousSoftwareOrg.transact().BecomeMemberCandidate("0x") # memNo:1 + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[2] + tx = autonomousSoftwareOrg.transact().BecomeMemberCandidate("0x") # memNo:2 + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[3] + tx = autonomousSoftwareOrg.transact().BecomeMemberCandidate("0x") # memNo:3 + contract_address = chain.wait.for_receipt(tx) + + memberInfoLength = autonomousSoftwareOrg.call().getMemberInfoLength() + print(memberInfoLength) + + web3.eth.defaultAccount = accounts[0] + url, memberaddr, votecount = autonomousSoftwareOrg.call().getCandidateMemberInfo(3) + print(url + "|" + memberaddr) + + web3.eth.defaultAccount = accounts[0] + # 0=>1 + tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(2) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[1] + # 1=>2 + tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(3) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[0] + # 0=>2 + tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(3) + contract_address = chain.wait.for_receipt(tx) + + # Member-2 became valid member + web3.eth.defaultAccount = accounts[0] + url, memberaddr, votecount = autonomousSoftwareOrg.call().getMemberInfo(2) + print(url + "|" + memberaddr + "|" + str(votecount)) + + ( + softwarename, + balance, + nummembers, + M, + N, + ) = autonomousSoftwareOrg.call().getAutonomousSoftwareOrgInfo() + print( + "name: " + + softwarename + + " |balance: " + + str(balance) + + " |numMembers: " + + str(nummembers) + + " |M: " + + str(M) + + " |N:" + + str(N) + ) + + web3.eth.defaultAccount = accounts[2] + url, memberaddr, votecount = autonomousSoftwareOrg.call().getMemberInfo(2) + print(url + "|" + memberaddr + "|vv" + str(votecount)) + + web3.eth.defaultAccount = accounts[0] + tx = autonomousSoftwareOrg.transact().DelVoteMemberCandidate(3) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[0] + # 0 => 3 memNo:3 votes + tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(3) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[2] + url, memberaddr, votecount = autonomousSoftwareOrg.call().getMemberInfo(2) + print(url + "|" + memberaddr) + + ( + softwarename, + balance, + nummembers, + M, + N, + ) = autonomousSoftwareOrg.call().getAutonomousSoftwareOrgInfo() + print( + "name: " + + softwarename + + " |balance: " + + str(balance) + + " |numMembers: " + + str(nummembers) + + " |M: " + + str(M) + + " |N:" + + str(N) + ) + + set_txn_hash = autonomousSoftwareOrg.transact( + {"from": accounts[5], "value": web3.toWei(2, "wei")} + ).Donate() + contract_address = chain.wait.for_receipt(tx) + + set_txn_hash = autonomousSoftwareOrg.transact( + {"from": accounts[6], "value": web3.toWei(2, "wei")} + ).Donate() + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[2] + set_txn_hash = autonomousSoftwareOrg.transact().ProposeProposal( + "Prop0", "0x", 0, 4, 14 + ) + contract_address = chain.wait.for_receipt(tx) + + ( + title, + url, + prophash, + requestedfund, + deadline, + withdrawn, + votecount, + ) = autonomousSoftwareOrg.call().getProposal(0) + print( + title + + "|" + + url + + "|" + + str(requestedfund) + + " " + + str(deadline) + + " " + + str(withdrawn) + ) + + web3.eth.defaultAccount = accounts[0] + # vote 1 + set_txn_hash = autonomousSoftwareOrg.transact().VoteForProposal(0) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[1] + set_txn_hash = autonomousSoftwareOrg.transact().VoteForProposal(0) + contract_address = chain.wait.for_receipt(tx) + + web3.eth.defaultAccount = accounts[2] + # vote 2 + set_txn_hash = autonomousSoftwareOrg.transact().WithdrawProposalFund(0) + # fails not enough vote. + contract_address = chain.wait.for_receipt(tx) + + ( + title, + url, + prophash, + requestedfund, + deadline, + withdrawn, + votecount, + ) = autonomousSoftwareOrg.call().getProposal(0) + print( + title + + "|" + + url + + "|" + + str(requestedfund) + + " " + + str(deadline) + + " " + + str(withdrawn) + ) + + # web3.eth.defaultAccount = accounts[0]; + # set_txn_hash = autonomousSoftwareOrg.transact().WithdrawProposalFund(0); #fails not enough vote. + # contract_address = chain.wait.for_receipt(tx) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b621975 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 + +import pytest + + +@pytest.fixture(scope="function", autouse=True) +def isolate(fn_isolation): + # perform a chain rewind after completing each test, to ensure proper isolation + # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures + pass + + +@pytest.fixture(scope="module") +def token(Token, accounts): + return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]}) + + +@pytest.fixture(scope="module") +def _Auto(AutonomousSoftwareOrg, accounts): + # _cfg.TOKEN = tx = USDTmy.deploy({"from": accounts[0]}) + # print(tx.address) + # accounts[0].deploy(Lib) + yield AutonomousSoftwareOrg.deploy("0x01234", 2, 3, "0x", {"from": accounts[0]}) diff --git a/tests/test_approve.py b/tests/test_approve.py new file mode 100644 index 0000000..85ec4f2 --- /dev/null +++ b/tests/test_approve.py @@ -0,0 +1,53 @@ +#!/usr/bin/python3 + +import pytest + + +@pytest.mark.parametrize("idx", range(5)) +def test_initial_approval_is_zero(token, accounts, idx): + assert token.allowance(accounts[0], accounts[idx]) == 0 + + +def test_approve(token, accounts): + token.approve(accounts[1], 10**19, {'from': accounts[0]}) + + assert token.allowance(accounts[0], accounts[1]) == 10**19 + + +def test_modify_approve(token, accounts): + token.approve(accounts[1], 10**19, {'from': accounts[0]}) + token.approve(accounts[1], 12345678, {'from': accounts[0]}) + + assert token.allowance(accounts[0], accounts[1]) == 12345678 + + +def test_revoke_approve(token, accounts): + token.approve(accounts[1], 10**19, {'from': accounts[0]}) + token.approve(accounts[1], 0, {'from': accounts[0]}) + + assert token.allowance(accounts[0], accounts[1]) == 0 + + +def test_approve_self(token, accounts): + token.approve(accounts[0], 10**19, {'from': accounts[0]}) + + assert token.allowance(accounts[0], accounts[0]) == 10**19 + + +def test_only_affects_target(token, accounts): + token.approve(accounts[1], 10**19, {'from': accounts[0]}) + + assert token.allowance(accounts[1], accounts[0]) == 0 + + +def test_returns_true(token, accounts): + tx = token.approve(accounts[1], 10**19, {'from': accounts[0]}) + + assert tx.return_value is True + + +def test_approval_event_fires(accounts, token): + tx = token.approve(accounts[1], 10**19, {'from': accounts[0]}) + + assert len(tx.events) == 1 + assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19] diff --git a/tests/test_transfer.py b/tests/test_transfer.py new file mode 100644 index 0000000..4cdd9e8 --- /dev/null +++ b/tests/test_transfer.py @@ -0,0 +1,16 @@ +#!/usr/bin/python3 +import brownie +import pytest + +auto = None + + +@pytest.fixture(scope="module", autouse=True) +def my_own_session_run_at_beginning(_Auto): + global auto # type: ignore + auto = _Auto + + +def test_AutonomousSoftwareOrg(accounts, token): + print(auto.getAutonomousSoftwareOrgInfo()) + breakpoint() # DEBUG diff --git a/tests/test_transferFrom.py b/tests/test_transferFrom.py new file mode 100644 index 0000000..fcb37cc --- /dev/null +++ b/tests/test_transferFrom.py @@ -0,0 +1,164 @@ +#!/usr/bin/python3 +import brownie + + +def test_sender_balance_decreases(accounts, token): + sender_balance = token.balanceOf(accounts[0]) + amount = sender_balance // 4 + + token.approve(accounts[1], amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert token.balanceOf(accounts[0]) == sender_balance - amount + + +def test_receiver_balance_increases(accounts, token): + receiver_balance = token.balanceOf(accounts[2]) + amount = token.balanceOf(accounts[0]) // 4 + + token.approve(accounts[1], amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert token.balanceOf(accounts[2]) == receiver_balance + amount + + +def test_caller_balance_not_affected(accounts, token): + caller_balance = token.balanceOf(accounts[1]) + amount = token.balanceOf(accounts[0]) + + token.approve(accounts[1], amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert token.balanceOf(accounts[1]) == caller_balance + + +def test_caller_approval_affected(accounts, token): + approval_amount = token.balanceOf(accounts[0]) + transfer_amount = approval_amount // 4 + + token.approve(accounts[1], approval_amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], transfer_amount, {'from': accounts[1]}) + + assert token.allowance(accounts[0], accounts[1]) == approval_amount - transfer_amount + + +def test_receiver_approval_not_affected(accounts, token): + approval_amount = token.balanceOf(accounts[0]) + transfer_amount = approval_amount // 4 + + token.approve(accounts[1], approval_amount, {'from': accounts[0]}) + token.approve(accounts[2], approval_amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], transfer_amount, {'from': accounts[1]}) + + assert token.allowance(accounts[0], accounts[2]) == approval_amount + + +def test_total_supply_not_affected(accounts, token): + total_supply = token.totalSupply() + amount = token.balanceOf(accounts[0]) + + token.approve(accounts[1], amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert token.totalSupply() == total_supply + + +def test_returns_true(accounts, token): + amount = token.balanceOf(accounts[0]) + token.approve(accounts[1], amount, {'from': accounts[0]}) + tx = token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert tx.return_value is True + + +def test_transfer_full_balance(accounts, token): + amount = token.balanceOf(accounts[0]) + receiver_balance = token.balanceOf(accounts[2]) + + token.approve(accounts[1], amount, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert token.balanceOf(accounts[0]) == 0 + assert token.balanceOf(accounts[2]) == receiver_balance + amount + + +def test_transfer_zero_tokens(accounts, token): + sender_balance = token.balanceOf(accounts[0]) + receiver_balance = token.balanceOf(accounts[2]) + + token.approve(accounts[1], sender_balance, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[2], 0, {'from': accounts[1]}) + + assert token.balanceOf(accounts[0]) == sender_balance + assert token.balanceOf(accounts[2]) == receiver_balance + + +def test_transfer_zero_tokens_without_approval(accounts, token): + sender_balance = token.balanceOf(accounts[0]) + receiver_balance = token.balanceOf(accounts[2]) + + token.transferFrom(accounts[0], accounts[2], 0, {'from': accounts[1]}) + + assert token.balanceOf(accounts[0]) == sender_balance + assert token.balanceOf(accounts[2]) == receiver_balance + + +def test_insufficient_balance(accounts, token): + balance = token.balanceOf(accounts[0]) + + token.approve(accounts[1], balance + 1, {'from': accounts[0]}) + with brownie.reverts(): + token.transferFrom(accounts[0], accounts[2], balance + 1, {'from': accounts[1]}) + + +def test_insufficient_approval(accounts, token): + balance = token.balanceOf(accounts[0]) + + token.approve(accounts[1], balance - 1, {'from': accounts[0]}) + with brownie.reverts(): + token.transferFrom(accounts[0], accounts[2], balance, {'from': accounts[1]}) + + +def test_no_approval(accounts, token): + balance = token.balanceOf(accounts[0]) + + with brownie.reverts(): + token.transferFrom(accounts[0], accounts[2], balance, {'from': accounts[1]}) + + +def test_revoked_approval(accounts, token): + balance = token.balanceOf(accounts[0]) + + token.approve(accounts[1], balance, {'from': accounts[0]}) + token.approve(accounts[1], 0, {'from': accounts[0]}) + + with brownie.reverts(): + token.transferFrom(accounts[0], accounts[2], balance, {'from': accounts[1]}) + + +def test_transfer_to_self(accounts, token): + sender_balance = token.balanceOf(accounts[0]) + amount = sender_balance // 4 + + token.approve(accounts[0], sender_balance, {'from': accounts[0]}) + token.transferFrom(accounts[0], accounts[0], amount, {'from': accounts[0]}) + + assert token.balanceOf(accounts[0]) == sender_balance + assert token.allowance(accounts[0], accounts[0]) == sender_balance - amount + + +def test_transfer_to_self_no_approval(accounts, token): + amount = token.balanceOf(accounts[0]) + + with brownie.reverts(): + token.transferFrom(accounts[0], accounts[0], amount, {'from': accounts[0]}) + + +def test_transfer_event_fires(accounts, token): + amount = token.balanceOf(accounts[0]) + + token.approve(accounts[1], amount, {'from': accounts[0]}) + tx = token.transferFrom(accounts[0], accounts[2], amount, {'from': accounts[1]}) + + assert len(tx.events) == 1 + assert tx.events["Transfer"].values() == [accounts[0], accounts[2], amount] diff --git a/token/.gitattributes b/token/.gitattributes new file mode 100644 index 0000000..52031de --- /dev/null +++ b/token/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity diff --git a/token/.github/workflows/main.yaml b/token/.github/workflows/main.yaml new file mode 100644 index 0000000..855521a --- /dev/null +++ b/token/.github/workflows/main.yaml @@ -0,0 +1,42 @@ +on: + push: + pull_request: + schedule: + # run this workflow every Monday at 1PM UTC + - cron: "* 13 * * 1" + +name: main workflow + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Cache Solidity Installations + uses: actions/cache@v1 + with: + path: ~/.solcx + key: ${{ runner.os }}-solcx-cache + + - name: Setup Node.js + uses: actions/setup-node@v1 + + - name: Install Ganache + run: npm install -g ganache-cli@6.8.2 + + - name: Setup Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Install Requirements + run: pip install -r requirements.txt + + - name: Run Tests + run: brownie test -C diff --git a/token/.gitignore b/token/.gitignore new file mode 100644 index 0000000..a418c16 --- /dev/null +++ b/token/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +.history +.hypothesis/ +build/ +reports/ From b3bc5425b1fb535a183a5f6af83f41f372ecc31d Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Sat, 18 Nov 2023 13:13:11 +0300 Subject: [PATCH 02/10] Fix some bugs, minor improvements, and more --- README.html | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 15 ---- README.org | 13 +++ 3 files changed, 265 insertions(+), 15 deletions(-) create mode 100644 README.html delete mode 100644 README.md create mode 100644 README.org diff --git a/README.html b/README.html new file mode 100644 index 0000000..5fe7ffa --- /dev/null +++ b/README.html @@ -0,0 +1,252 @@ + + + + + + + + + + + + + +
+ +
+

1. AutonomousSoftwareOrg

+
+

+Smart Contract Based Autonomous Organization for Sustainable Software +

+
+ +
+

1.1. About

+
+

+AutonomousSoftwareOrg is a Solidity smart contract that implements an autonomous software organization to be used by software developers. +

+ +

+Design of a Smart Contract Based Autonomous Organization for Sustainable Software Paper Link: https://ieeexplore.ieee.org/document/8109181 +

+
+ +
+

1.1.1. AutonomousSoftwareOrg Smart Contract Address on Bloxberg

+
+
+
0xabcd...
+
+
+
+
+
+
+
+
+

Author: Alper Alimoglu

+

Created: 2023-11-18 Sat 13:22

+
+ + diff --git a/README.md b/README.md deleted file mode 100644 index 355b36b..0000000 --- a/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# AutonomousSoftwareOrg -Smart Contract Based Autonomous Organization for Sustainable Software - -## About -AutonomousSoftwareOrg is a Solidity smart contract that implements an autonomous software organization to be used by software developers. - -**Design of a Smart Contract Based Autonomous Organization for Sustainable Software Paper Link:** [https://ieeexplore.ieee.org/document/8109181](https://ieeexplore.ieee.org/document/8109181) - -### Connect to AutonomousSoftwareOrg Contract on our local Ethereum based blockchain: - -```bash -address="0xa196dc4c924e027973c17ee75befaa1545a09e28"; -abi=[{"constant":true,"inputs":[],"name":"getSoftwareExecRecordLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"usedbysoftwareno","type":"uint256"}],"name":"getUsedBySoftware","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"members","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"propno","type":"uint256"}],"name":"VoteForProposal","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"title","type":"bytes32"},{"name":"url","type":"bytes32"},{"name":"prophash","type":"uint256"},{"name":"requestedfund","type":"uint256"},{"name":"deadline","type":"uint256"}],"name":"ProposeProposal","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"membersTryOut","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"UseBySoftware","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getDonationLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"citeno","type":"uint256"}],"name":"getCitation","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"softwareversion","type":"bytes32"},{"name":"url","type":"bytes32"},{"name":"inputhash","type":"uint256"},{"name":"outputhash","type":"uint256"}],"name":"addSoftwareExecRecord","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"url","type":"bytes32"}],"name":"BecomeMemberCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getUsedBySoftwareLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint32"}],"name":"getSoftwareVersionRecords","outputs":[{"name":"","type":"address"},{"name":"","type":"bytes32"},{"name":"","type":"bytes32"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"M","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"doinumber","type":"bytes32"}],"name":"Cite","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"donationno","type":"uint256"}],"name":"getDonationInfo","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getAutonomousSoftwareOrgInfo","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"propno","type":"uint256"}],"name":"WithdrawProposalFund","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"memberno","type":"uint256"}],"name":"VoteMemberCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"nummembers","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"memberno","type":"uint256"}],"name":"getMemberInfo","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getMemberInfoLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"balance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getProposalsLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint32"}],"name":"getSoftwareExecRecord","outputs":[{"name":"","type":"address"},{"name":"","type":"bytes32"},{"name":"","type":"bytes32"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"propno","type":"uint256"}],"name":"getProposal","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bytes32"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"bool"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"url","type":"bytes32"},{"name":"version","type":"bytes32"},{"name":"sourcehash","type":"uint256"}],"name":"addSoftwareVersionRecord","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"N","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"softwarename","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"Donate","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"memberno","type":"uint256"}],"name":"getCandidateMemberInfo","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"geSoftwareVersionRecordsLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"memberno","type":"uint256"}],"name":"DelVoteMemberCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCitationLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"name","type":"bytes32"},{"name":"m","type":"uint8"},{"name":"n","type":"uint8"},{"name":"url","type":"bytes32"}],"payable":false,"type":"constructor"},{"payable":false,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"submitter","type":"address"},{"indexed":false,"name":"softwareversion","type":"bytes32"},{"indexed":false,"name":"url","type":"bytes32"},{"indexed":false,"name":"inputhash","type":"uint256"},{"indexed":false,"name":"outputhash","type":"uint256"}],"name":"LogSoftwareExecRecord","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"submitter","type":"address"},{"indexed":false,"name":"url","type":"bytes32"},{"indexed":false,"name":"version","type":"bytes32"},{"indexed":false,"name":"sourcehash","type":"uint256"}],"name":"LogSoftwareVersionRecord","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"propNo","type":"uint256"},{"indexed":false,"name":"title","type":"bytes32"},{"indexed":false,"name":"ipfsHash","type":"bytes32"},{"indexed":false,"name":"requestedFund","type":"uint256"},{"indexed":false,"name":"deadline","type":"uint256"}],"name":"LogPropose","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"votecount","type":"uint256"},{"indexed":false,"name":"blocknum","type":"uint256"},{"indexed":false,"name":"voter","type":"address"}],"name":"LogProposalVote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"donor","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"blknum","type":"uint256"}],"name":"LogDonation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"propno","type":"uint256"},{"indexed":false,"name":"requestedfund","type":"uint256"},{"indexed":false,"name":"blocknum","type":"uint256"},{"indexed":false,"name":"proposalOwner","type":"address"}],"name":"LogWithdrawProposalFund","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"memberno","type":"uint256"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"votecount","type":"uint256"}],"name":"LogVoteMemberCandidate","type":"event"}] -var AutonomousSoftwareOrg = web3.eth.contract(abi).at(address); -``` diff --git a/README.org b/README.org new file mode 100644 index 0000000..fbafddc --- /dev/null +++ b/README.org @@ -0,0 +1,13 @@ +* AutonomousSoftwareOrg +Smart Contract Based Autonomous Organization for Sustainable Software + +** About +AutonomousSoftwareOrg is a Solidity smart contract that implements an autonomous software organization to be used by software developers. + +*Design of a Smart Contract Based Autonomous Organization for Sustainable Software Paper Link:* [[https://ieeexplore.ieee.org/document/8109181]] + +*** AutonomousSoftwareOrg Smart Contract Address on Bloxberg + +#+begin_src bash +0xabcd... +#+end_src From ad6f6f2ebef04809de5a7a47b02c826e38e45e83 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Tue, 21 Nov 2023 20:44:37 +0000 Subject: [PATCH 03/10] Update version --- contracts/AutonomousSoftwareOrg.sol | 112 ++++++++++++++++++---------- tests/test_transfer.py | 25 +++++++ 2 files changed, 96 insertions(+), 41 deletions(-) diff --git a/contracts/AutonomousSoftwareOrg.sol b/contracts/AutonomousSoftwareOrg.sol index 16da1f2..447d230 100644 --- a/contracts/AutonomousSoftwareOrg.sol +++ b/contracts/AutonomousSoftwareOrg.sol @@ -1,34 +1,34 @@ -pragma solidity ^0.6.0; - // SPDX-License-Identifier: MIT -contract AutonomousSoftwareOrg { +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; +contract AutonomousSoftwareOrg { struct SoftwareVersionRecord { address submitter; - bytes32 url; - bytes32 version; - uint256 sourceCodeHash; + string url; + string version; + bytes32 sourceCodeHash; } struct SoftwareExecRecord { address submitter; - bytes32 softwareVersion; - bytes32 url; - uint256 inputHash; - uint256 outputHash; + string softwareVersion; + string url; + bytes32[] inputHash; + bytes32[] outputHash; } struct MemberInfo { - bytes32 url; + string url; address memberAddr; uint voteCount; mapping(address => bool) voted; } struct Proposal { - bytes32 title; - bytes32 url; + string title; + string url; uint256 propHash; address proposer; uint requestedFund; @@ -44,7 +44,7 @@ contract AutonomousSoftwareOrg { uint blkno; } - bytes32 public softwareName; + string public softwareName; uint public balance; uint public numMembers; @@ -55,6 +55,9 @@ contract AutonomousSoftwareOrg { mapping(address => uint) public members; mapping(address => bool) public membersTryOut; + mapping(bytes32 => uint) _hashToRoc; + mapping(uint => bytes32) _rocToHash; + SoftwareVersionRecord[] versions; SoftwareExecRecord[] execRecords; @@ -64,13 +67,14 @@ contract AutonomousSoftwareOrg { bytes32[] citations; address[] usedBySoftware; - event LogSoftwareExecRecord(address submitter, bytes32 softwareVersion, bytes32 url, uint256 inputHash, uint256 outputHash); - event LogSoftwareVersionRecord(address submitter, bytes32 url, bytes32 version, uint256 sourceCodeHash); - event LogPropose(uint propNo,bytes32 title, bytes32 ipfsHash, uint requestedFund, uint deadline); + event LogSoftwareExecRecord(address submitter, string softwareVersion, string url, bytes32[] inputHash, bytes32[] outputHash); + event LogSoftwareVersionRecord(address submitter, string url, string version, bytes32 sourceCodeHash); + event LogPropose(uint propNo, string title, string url, uint requestedFund, uint deadline); event LogProposalVote(uint voteCount, uint blockNum, address voter); event LogDonation(address donor,uint amount,uint blknum); event LogWithdrawProposalFund(uint propNo, uint requestedFund, uint blockNum, address proposalOwner); event LogVoteMemberCandidate(uint memberNo,address voter,uint voteCount); + event LogHashROC(address indexed provider, bytes32 hash, uint32 roc, bool isIPFS); modifier enough_fund_balance(uint propNo) { require(balance >= proposals[propNo].requestedFund); @@ -143,12 +147,15 @@ contract AutonomousSoftwareOrg { _; } - constructor(bytes32 name,uint8 m,uint8 n, bytes32 url) public { + constructor(string memory name, uint8 m, uint8 n, string memory url) { if (m > n) revert(); softwareName = name; - membersInfo.push(MemberInfo(url, msg.sender, 0)); + MemberInfo storage _membersInfo = membersInfo.push(); + _membersInfo.url = url; + _membersInfo.memberAddr = msg.sender; + _membersInfo.voteCount = 0; members[msg.sender] = membersInfo.length; balance = 0; numMembers = 1; @@ -156,10 +163,18 @@ contract AutonomousSoftwareOrg { N = n; } - function ProposeProposal(bytes32 title, bytes32 url, uint256 propHash, uint requestedFund, uint deadline) public + function ProposeProposal(string memory title, string memory url, uint256 propHash, uint requestedFund, uint deadline) public member(msg.sender) validDeadline(deadline) { - proposals.push(Proposal(title,url, propHash, msg.sender, requestedFund, deadline, 0, false)); - emit LogPropose( proposals.length, title, url, requestedFund, deadline); + Proposal storage _proposal = proposals.push(); + _proposal.title = title; + _proposal.url = url; + _proposal.propHash = propHash; + _proposal.proposer = msg.sender; + _proposal.requestedFund = requestedFund; + _proposal.deadline = deadline; + _proposal.voteCount = 0; + _proposal.withdrawn = false; + emit LogPropose(proposals.length, title, url, requestedFund, deadline); } function VoteForProposal(uint propNo) public @@ -175,23 +190,26 @@ contract AutonomousSoftwareOrg { member(msg.sender) enough_fund_balance(propNo) proposalOwner(propNo) proposalMajority(propNo) { balance -= proposals[propNo].requestedFund; - if (proposals[propNo].withdrawn == true || ! msg.sender.send(proposals[propNo].requestedFund)) { + if (proposals[propNo].withdrawn == true) { revert(); } + payable(msg.sender).transfer(proposals[propNo].requestedFund); proposals[propNo].withdrawn = true; emit LogWithdrawProposalFund(propNo,proposals[propNo].requestedFund,block.number,msg.sender); } - function BecomeMemberCandidate(bytes32 url) public + function BecomeMemberCandidate(string memory url) public notMember(msg.sender) { if(membersTryOut[msg.sender] == true) revert(); - membersInfo.push(MemberInfo(url, msg.sender, 0)); + MemberInfo storage _memberInfo = membersInfo.push(); + _memberInfo.url = url; + _memberInfo.memberAddr = msg.sender; + _memberInfo.voteCount = 0; membersTryOut[msg.sender] = true; } - function VoteMemberCandidate(uint memberNo) public validMemberNo(memberNo) member(msg.sender) notVotedForMember(memberNo) { membersInfo[memberNo-1].voted[msg.sender] = true; @@ -234,19 +252,20 @@ contract AutonomousSoftwareOrg { usedBySoftware.push(addr); } - function addSoftwareExecRecord(bytes32 softwareVersion, bytes32 url, uint256 inputHash,uint256 outputHash) + function addSoftwareExecRecord(string memory softwareVersion, string memory url, bytes32[] memory inputHash, bytes32[] memory outputHash) public member(msg.sender) { execRecords.push(SoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash)); emit LogSoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash); } - function addSoftwareVersionRecord(bytes32 url, bytes32 version, uint256 sourceCodeHash) public { + function addSoftwareVersionRecord(string memory url, string memory version, bytes32 sourceCodeHash) + public { versions.push(SoftwareVersionRecord(msg.sender, url, version, sourceCodeHash)); emit LogSoftwareVersionRecord(msg.sender, url, version, sourceCodeHash); } function getSoftwareExecRecord(uint32 id) - public view returns(address,bytes32,bytes32,uint256,uint256) { + public view returns(address, string memory, string memory, bytes32[] memory, bytes32[] memory) { return(execRecords[id].submitter, execRecords[id].softwareVersion, execRecords[id].url, @@ -260,7 +279,7 @@ contract AutonomousSoftwareOrg { } function getSoftwareVersionRecords(uint32 id) - public view returns(address,bytes32,bytes32,uint256) { + public view returns(address, string memory, string memory, bytes32) { return(versions[id].submitter, versions[id].url, versions[id].version, @@ -273,7 +292,7 @@ contract AutonomousSoftwareOrg { } function getAutonomousSoftwareOrgInfo() - public view returns (bytes32,uint,uint,uint,uint) { + public view returns (string memory, uint, uint, uint, uint) { return (softwareName, balance, numMembers, M, N); } @@ -284,15 +303,15 @@ contract AutonomousSoftwareOrg { function getMemberInfo(uint memberNo) member(membersInfo[memberNo-1].memberAddr) - public view returns (bytes32,address, uint) { - return (membersInfo[memberNo-1].url, - membersInfo[memberNo-1].memberAddr, - membersInfo[memberNo-1].voteCount); + public view returns (string memory, address, uint) { + return (membersInfo[memberNo - 1].url, + membersInfo[memberNo - 1].memberAddr, + membersInfo[memberNo - 1].voteCount); } function getCandidateMemberInfo(uint memberNo) - notMember(membersInfo[memberNo-1].memberAddr) - public view returns (bytes32,address, uint) { + notMember(membersInfo[memberNo - 1].memberAddr) + public view returns (string memory, address, uint) { return (membersInfo[memberNo-1].url, membersInfo[memberNo-1].memberAddr, membersInfo[memberNo-1].voteCount); @@ -304,7 +323,7 @@ contract AutonomousSoftwareOrg { } function getProposal(uint propNo) - public view returns (bytes32, bytes32, uint256, uint, uint, bool, uint) { + public view returns (string memory, string memory, uint256, uint, uint, bool, uint) { return (proposals[propNo].title, proposals[propNo].url, proposals[propNo].propHash, @@ -320,7 +339,7 @@ contract AutonomousSoftwareOrg { } function getDonationInfo(uint donationNo) - public view returns (address,uint,uint) { + public view returns (address, uint, uint) { return (donations[donationNo].donor, donations[donationNo].amnt, donations[donationNo].blkno); @@ -331,9 +350,9 @@ contract AutonomousSoftwareOrg { return (citations.length); } - function getCitation(uint citeno) + function getCitation(uint citeNo) public view returns (bytes32) { - return (citations[citeno]); + return (citations[citeNo]); } function getUsedBySoftwareLength() @@ -345,4 +364,15 @@ contract AutonomousSoftwareOrg { public view returns (address) { return (usedBySoftware[usedBySoftwareNo]); } + + // ------------------------------------------------------------------------------ + + function hashToRoc(bytes32 hash, uint32 roc, bool isIPFS) public returns (bool) { + if (_hashToRoc[hash] == 0) { + _hashToRoc[hash] = roc; + _rocToHash[roc] = hash; + emit LogHashROC(msg.sender, hash, roc, isIPFS); + } + return true; + } } diff --git a/tests/test_transfer.py b/tests/test_transfer.py index 4cdd9e8..54390bf 100644 --- a/tests/test_transfer.py +++ b/tests/test_transfer.py @@ -1,6 +1,9 @@ #!/usr/bin/python3 + +from broker._utils._log import log import brownie import pytest +import random auto = None @@ -11,6 +14,28 @@ def my_own_session_run_at_beginning(_Auto): auto = _Auto +def md5_hash(): + _hash = random.getrandbits(128) + return "%032x" % _hash + + def test_AutonomousSoftwareOrg(accounts, token): print(auto.getAutonomousSoftwareOrgInfo()) + _hash = md5_hash() + auto.addSoftwareVersionRecord("alper.com", "1.0.0", _hash, {"from": accounts[0]}) + output = auto.getSoftwareVersionRecords(0) + log(output[1]) + input_hash = [md5_hash(), "0xabcd"] + output_hash = [md5_hash(), "0xabcde"] + auto.addSoftwareExecRecord("1.0.0", "alper.com", input_hash, output_hash) + + input_hash = [md5_hash(), md5_hash(), md5_hash()] + output_hash = [md5_hash(), md5_hash(), md5_hash()] + auto.addSoftwareExecRecord("1.0.0", "alper.com", input_hash, output_hash) + + output = auto.getSoftwareExecRecord(0) + log(output) + output = auto.getSoftwareExecRecord(1) + log(output) + breakpoint() # DEBUG From b64d6fcf05e77595912b429336a3cffdb9d462f7 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Tue, 28 Nov 2023 13:26:31 +0000 Subject: [PATCH 04/10] Add incoming outgoing structure into the smart contract --- contracts/AutonomousSoftwareOrg.sol | 46 +- contracts/EBlocBrokerBase.sol | 93 +++ contracts/ERC20/Context.sol | 24 + contracts/ERC20/ERC20.sol | 427 ++++++++++ contracts/ERC20/IERC20.sol | 82 ++ contracts/ERC20/IERC20Metadata.sol | 28 + contracts/ERC20/Ownable.sol | 97 +++ contracts/ERC20/USDTmy.sol | 26 + contracts/Lib.sol | 404 ++++++++++ contracts/SafeMath.sol | 135 +++- contracts/eBlocBroker.sol | 1117 +++++++++++++++++++++++++++ contracts/eBlocBrokerInterface.sol | 75 ++ tests/conftest.py | 17 +- tests/test_transfer.py | 81 +- 14 files changed, 2615 insertions(+), 37 deletions(-) create mode 100644 contracts/EBlocBrokerBase.sol create mode 100644 contracts/ERC20/Context.sol create mode 100644 contracts/ERC20/ERC20.sol create mode 100644 contracts/ERC20/IERC20.sol create mode 100644 contracts/ERC20/IERC20Metadata.sol create mode 100644 contracts/ERC20/Ownable.sol create mode 100644 contracts/ERC20/USDTmy.sol create mode 100644 contracts/Lib.sol create mode 100644 contracts/eBlocBroker.sol create mode 100644 contracts/eBlocBrokerInterface.sol diff --git a/contracts/AutonomousSoftwareOrg.sol b/contracts/AutonomousSoftwareOrg.sol index 447d230..8955482 100644 --- a/contracts/AutonomousSoftwareOrg.sol +++ b/contracts/AutonomousSoftwareOrg.sol @@ -3,6 +3,8 @@ pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; +import "./eBlocBroker.sol"; + contract AutonomousSoftwareOrg { struct SoftwareVersionRecord { address submitter; @@ -13,8 +15,8 @@ contract AutonomousSoftwareOrg { struct SoftwareExecRecord { address submitter; - string softwareVersion; - string url; + bytes32 sourceCodeHash; + uint32 index; bytes32[] inputHash; bytes32[] outputHash; } @@ -58,6 +60,9 @@ contract AutonomousSoftwareOrg { mapping(bytes32 => uint) _hashToRoc; mapping(uint => bytes32) _rocToHash; + mapping(bytes32 => mapping(uint32 => bytes32[])) incoming; + mapping(bytes32 => mapping(uint32 => bytes32[])) outgoing; + SoftwareVersionRecord[] versions; SoftwareExecRecord[] execRecords; @@ -67,7 +72,9 @@ contract AutonomousSoftwareOrg { bytes32[] citations; address[] usedBySoftware; - event LogSoftwareExecRecord(address submitter, string softwareVersion, string url, bytes32[] inputHash, bytes32[] outputHash); + address public eBlocBrokerAddress; + + event LogSoftwareExecRecord(address submitter, bytes32 sourceCodeHash, uint32 index, bytes32[] inputHash, bytes32[] outputHash); event LogSoftwareVersionRecord(address submitter, string url, string version, bytes32 sourceCodeHash); event LogPropose(uint propNo, string title, string url, uint requestedFund, uint deadline); event LogProposalVote(uint voteCount, uint blockNum, address voter); @@ -147,7 +154,7 @@ contract AutonomousSoftwareOrg { _; } - constructor(string memory name, uint8 m, uint8 n, string memory url) { + constructor(string memory name, uint8 m, uint8 n, string memory url, address _eBlocBrokerAddress) { if (m > n) revert(); @@ -161,6 +168,8 @@ contract AutonomousSoftwareOrg { numMembers = 1; M = m; N = n; + + eBlocBrokerAddress = _eBlocBrokerAddress; } function ProposeProposal(string memory title, string memory url, uint256 propHash, uint requestedFund, uint deadline) public @@ -214,7 +223,6 @@ contract AutonomousSoftwareOrg { member(msg.sender) notVotedForMember(memberNo) { membersInfo[memberNo-1].voted[msg.sender] = true; membersInfo[memberNo-1].voteCount++; - if ((membersInfo[memberNo - 1].voteCount) * N >= (numMembers * M)) { if (members[membersInfo[memberNo - 1].memberAddr] == 0) { members[membersInfo[memberNo - 1].memberAddr] = memberNo; @@ -252,10 +260,26 @@ contract AutonomousSoftwareOrg { usedBySoftware.push(addr); } - function addSoftwareExecRecord(string memory softwareVersion, string memory url, bytes32[] memory inputHash, bytes32[] memory outputHash) + function addSoftwareExecRecord(bytes32 sourceCodeHash, uint32 index, bytes32[] memory inputHash, bytes32[] memory outputHash) public member(msg.sender) { - execRecords.push(SoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash)); - emit LogSoftwareExecRecord(msg.sender, softwareVersion, url, inputHash, outputHash); + if (eBlocBroker(eBlocBrokerAddress).doesProviderExist(msg.sender)) { + for (uint256 i = 0; i < inputHash.length; i++) { + incoming[sourceCodeHash][index].push(inputHash[i]); + } + for (uint256 i = 0; i < outputHash.length; i++) { + outgoing[sourceCodeHash][index].push(outputHash[i]); + } + // execRecords.push(SoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash)); + emit LogSoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash); + } + } + + function getIncomings(bytes32 sourceCodeHash, uint32 index) public view returns(bytes32[] memory){ + return incoming[sourceCodeHash][index]; + } + + function getOutgoings(bytes32 sourceCodeHash, uint32 index) public view returns(bytes32[] memory){ + return outgoing[sourceCodeHash][index]; } function addSoftwareVersionRecord(string memory url, string memory version, bytes32 sourceCodeHash) @@ -265,10 +289,10 @@ contract AutonomousSoftwareOrg { } function getSoftwareExecRecord(uint32 id) - public view returns(address, string memory, string memory, bytes32[] memory, bytes32[] memory) { + public view returns(address, bytes32, uint32, bytes32[] memory, bytes32[] memory) { return(execRecords[id].submitter, - execRecords[id].softwareVersion, - execRecords[id].url, + execRecords[id].sourceCodeHash, + execRecords[id].index, execRecords[id].inputHash, execRecords[id].outputHash); } diff --git a/contracts/EBlocBrokerBase.sol b/contracts/EBlocBrokerBase.sol new file mode 100644 index 0000000..1424902 --- /dev/null +++ b/contracts/EBlocBrokerBase.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT + +/* + file: eBlocBrokerBase.sol + author: Alper Alimoglu + email: alper.alimoglu AT gmail.com +*/ + +pragma solidity >=0.7.0 <0.9.0; +import "./SafeMath.sol"; +import "./Lib.sol"; + +contract EBlocBrokerBase { + using SafeMath for uint256; + using SafeMath32 for uint32; + + address public ebb_owner; + address public tokenAddress; + address[] registeredProviders; // A dynamically-sized array of 'address' structs + uint8 constant BLOCK_TIME = 6; + uint32 constant ONE_HOUR_BLOCK_DURATION = 1 hours / BLOCK_TIME; // ~1 hour, average block time is 6 seconds + uint32 constant ONE_DAY = 1 days / BLOCK_TIME; + mapping(address => uint32) requesterCommittedBlock; // Block number when provider is registered in order the watch provider's event activity + mapping(address => Lib.Provider) providers; + mapping(address => uint32[]) pricesSetBlockNum; + mapping(address => bytes32) orcID; // Mapping from address of a requester or provider to its orcID + // mapping(string => mapping(uint32 => uint => Job) jobs; + + /** + * @dev Modifier to make a function callable only when given key is not zero + modifier whenKeyValid(string memory _key) { + require(bytes(_key).length > 0); // dev: Bad key + _; + } + */ + + /** + * @dev Modifier to make a function callable only when caller is registered as provider. + */ + modifier whenProviderRegistered() { + require(providers[msg.sender].committedBlock > 0); // dev: Not registered + _; + } + + /** + * @dev Modifier to make a function callable only when the provider is not registered. + */ + modifier whenProviderNotRegistered() { + require(providers[msg.sender].committedBlock == 0); // dev: Registered + _; + } + + /** + * @dev Modifier to make a function callable only when given timestamp is smaller than the block.timestamp. + */ + modifier whenBehindNow(uint256 timestamp) { + require(timestamp <= block.timestamp); // dev: Ahead block.timestamp + _; + } + + /** + * @dev Modifier to make a function callable only when the provider in running. + */ + modifier whenProviderRunning() { + require(providers[msg.sender].isRunning); // dev: Provider is not running + _; + } + + /** + * @dev Modifier to make a function callable only when the provider is suspended. + */ + modifier whenProviderSuspended() { + require(!providers[msg.sender].isRunning); // dev: Provider is not suspended + _; + } + + /** + * @dev Modifier to make a function callable only when stateCode is valid + */ + modifier validJobStateCode(Lib.JobStateCodes stateCode) { + /*stateCode cannot be NULL, COMPLETED, REFUNDED on setJobState call */ + require(uint8(stateCode) > 1 && uint8(stateCode) < 7); + _; + } + + /** + * @dev Modifier to make a function callable only when orcID is verified + */ + modifier whenOrcidNotVerified(address _user) { + require(orcID[_user] == 0); // dev: OrcID is already verified + _; + } +} diff --git a/contracts/ERC20/Context.sol b/contracts/ERC20/Context.sol new file mode 100644 index 0000000..f304065 --- /dev/null +++ b/contracts/ERC20/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/contracts/ERC20/ERC20.sol b/contracts/ERC20/ERC20.sol new file mode 100644 index 0000000..0dcd848 --- /dev/null +++ b/contracts/ERC20/ERC20.sol @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol) +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; +import "./IERC20Metadata.sol"; +import "./Context.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping(address => uint256) private balances; + mapping(address => mapping(address => uint256)) private _allowances; + uint256 private _totalSupply; + string private _name; + string private _symbol; + address private _owner; + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == _owner); // dev: Sender must be owner + _; + } + + /** + * @dev Return the owner of the eBlocBroker contract + */ + function getOwner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + * + * A mill = $0.001 = 1/1000 of a dollar. + * A cent = $0.01 = 1/100 of a dollar. + * A dime = $0.10 = 1/10 of a dollar. + */ + function decimals() public view virtual override returns (uint8) { + return 8; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + function _distributeTransfer(address to, uint256 amount) internal virtual returns (bool) { + _transfer(_owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + * Returns the remaining number of tokens that spender will be allowed to spend + * on behalf of owner through transferFrom. + * This is zero by default. + * This value changes when approve or transferFrom are called. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + balances[to] += amount; + } + + emit Transfer(from, to, amount); + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + _owner = account; + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + balances[account] += amount; + } + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + balances[account] = accountBalance - amount; + // Overflow not possible: amount <= accountBalance <= totalSupply. + _totalSupply -= amount; + } + + emit Transfer(account, address(0), amount); + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /* event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); */ + + /* /\** */ + /* * @dev Transfer ownership of the contract to a new account (`newOwner`). */ + /* * It can only be called by the current owner. */ + /* * @param newOwner The address to transfer ownership to. */ + /* *\/ */ + /* function transferOwnership(address newOwner) public onlyOwner { */ + /* require(newOwner != address(0), "Zero address"); */ + /* emit OwnershipTransferred(_owner, newOwner); */ + /* _owner = newOwner; */ + /* // TODO: transfer owner's balance as well */ + /* } */ +} diff --git a/contracts/ERC20/IERC20.sol b/contracts/ERC20/IERC20.sol new file mode 100644 index 0000000..b816bfe --- /dev/null +++ b/contracts/ERC20/IERC20.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} diff --git a/contracts/ERC20/IERC20Metadata.sol b/contracts/ERC20/IERC20Metadata.sol new file mode 100644 index 0000000..982bc39 --- /dev/null +++ b/contracts/ERC20/IERC20Metadata.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/contracts/ERC20/Ownable.sol b/contracts/ERC20/Ownable.sol new file mode 100644 index 0000000..cb7d891 --- /dev/null +++ b/contracts/ERC20/Ownable.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) + +pragma solidity ^0.8.19; + +import "./Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `_onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor(address initialOwner) { + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier _onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `_onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual _onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual _onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/contracts/ERC20/USDTmy.sol b/contracts/ERC20/USDTmy.sol new file mode 100644 index 0000000..fb0f778 --- /dev/null +++ b/contracts/ERC20/USDTmy.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./ERC20.sol"; +import "./Ownable.sol"; +import "./Context.sol"; + +contract USDTmy is Ownable, ERC20 { + constructor() ERC20("USDmy", "USDmy") Ownable(msg.sender){ + mint(msg.sender, 1000000000 * (10**uint256(decimals()))); + } + + function mint(address account, uint256 amount) public _onlyOwner { + _mint(account, amount); + } + + function burn(address account, uint256 amount) public _onlyOwner { + _burn(account, amount); + } + + /* function _distributeTransfer(address to, uint256 amount) internal virtual returns (bool) { */ + /* _transfer(_owner, to, amount); */ + /* return true; */ + /* } */ + +} diff --git a/contracts/Lib.sol b/contracts/Lib.sol new file mode 100644 index 0000000..99c139d --- /dev/null +++ b/contracts/Lib.sol @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: MIT + +/* + file: Lib.sol + author: Alper Alimoglu + email: alper.alimoglu AT gmail.com +*/ + +pragma solidity >=0.7.0 <0.9.0; + +library Lib { + enum CacheID { + PUBLIC, // 0 + PRIVATE // 1 + } + + enum CloudStorageID { + IPFS, // 0 + IPFS_GPG, // 1 + NONE, // 2 Request to use from registered or cached data + B2DROP, // 3 + GDRIVE // 4 + } + + /* Status of the submitted job Enum */ + enum JobStateCodes { + /* Following states {0, 1, 2} will allow to request refund */ + SUBMITTED, /* 0 Initial state */ + PENDING, + /* 1 Indicates when a request is receieved by the provider. The + * job is waiting for resource allocation. It will eventually + * run. */ + RUNNING, + /* 2 The job currently is allocated to a node and is + * running. Corresponding data files are downloaded and + * verified.*/ + /* Following states {3, 4, 5} used to prevent double spending */ + REFUNDED, /* 3 Indicates if job is refunded */ + CANCELLED, + /* 4 Job was explicitly cancelled by the requester or system + * administrator. The job may or may not have been + * initiated. Set by the requester*/ + COMPLETED, + /* 5 The job has completed successfully and deposit is paid + * to the provider */ + TIMEOUT, /* 6 Job terminated upon reaching its time limit. */ + COMPLETED_WAITING_ADDITIONAL_DATA_TRANSFER_OUT_DEPOSIT /* 7 */ + } + + struct JobArgument { + /* An Ethereum address value containing the Ethereum address of the + * provider that is requested to run the job. */ + address payable provider; + /* A uint32 value containing the block number when the requested + * provider set its prices most recent. */ + uint32 priceBlockIndex; + /* An array of uint8 values that denote whether the requester’s data is + stored and shared using either IPFS, B2DROP, IPFS (with GPG + encryption), or Google Drive. */ + uint8[] cloudStorageID; + /* An array of uint8 values that denote whether the requester’s data + will be cached privately within job owner's home directory, or + publicly for other requesters' access within a shared directory for + all the requesters. + */ + uint8[] cacheType; + /* An array of uint32 values that denote whether the provider’s + * registered data will be used or not. */ + uint32[] dataPricesSetBlockNum; + uint16[] core; + uint16[] runTime; + uint32 dataTransferOut; + uint256 jobPrice; + uint256 workflowId; + } + + struct JobIndexes { + uint32 index; + uint32 jobID; + uint32 endTimestamp; + uint32 dataTransferIn; + uint32 dataTransferOut; + uint32 elapsedTime; + uint256[] core; + uint256[] runTime; + uint8 finalize; // {0: entry-job, 1: in-between-job, 2 exit-job} + } + + struct DataInfo { + uint32 price; + uint32 commitmentBlockDur; + } + + struct Storage { + uint256 received; // received payment for storage usage + } + + struct JobStorage { + uint32 receivedBlock; + uint32 storageDuration; + bool isPrivate; + bool isVerifiedUsed; // Set to `true` if the provided used and verified the given code hash + //address owner; //Cloud be multiple owners + } + + struct RegisteredData { + uint32[] committedBlock; // Block number when data is registered + mapping(uint256 => DataInfo) dataInfo; + } + + struct Job { + JobStateCodes stateCode; // Assigned by the provider + uint32 startTimestamp; // Submitted job's starting universal time on the server side. Assigned by the provider + } + + // Submitted Job's information + struct Status { + uint32 dataTransferIn; + uint32 dataTransferOut; + uint32 pricesSetBlockNum; // When provider is submitted provider's most recent block number when its set or updated + uint256 cacheCost; + uint256 received; // paid amount (new owned) by the requester + address payable jobOwner; // Address of the client (msg.sender) has been stored + bytes32 sourceCodeHash; // keccak256 of the list of sourceCodeHash list concatinated with the cacheType list + bytes32 jobInfo; + uint256 receivedRegisteredDataFee; + mapping(uint256 => Job) jobs; + } + + struct ProviderInfo { + uint32 availableCore; + uint32 commitmentBlockDur; + uint256 priceCoreMin; + uint256 priceDataTransfer; + uint256 priceStorage; + uint256 priceCache; + } + + struct Provider { + uint32 committedBlock; // Block number when is registered in order the watch provider's event activity + bool isRunning; // Flag that checks is Provider running or not + mapping(uint256 => ProviderInfo) info; + mapping(bytes32 => JobStorage) jobSt; // Stored information related to job's storage time + mapping(bytes32 => RegisteredData) registeredData; + mapping(address => mapping(bytes32 => Storage)) storageInfo; + mapping(string => Status[]) jobStatus; // All submitted jobs into provider 's Status is accessible + mapping(bytes32 => uint) hashToROC; + LL receiptList; // receiptList will be use to check either job's start and end time overlapped or not + } + + struct Interval { + int32 core; // job's requested core number + uint32 next; // points to next the node + uint32 endpoint; + } + + struct IntervalArg { + uint32 startTimestamp; + uint32 endTimestamp; + int32 core; // job's requested core number + int256 availableCore; + } + + struct LL { + uint32 length; + uint32 tail; // tail of the linked list + mapping(uint256 => Interval) items; + } + + struct HashToROC { + mapping(bytes32 => uint) hashToROC; + mapping(uint => bytes32) rocToHash; + } + + /** + *@dev Invoked when registerProvider() function is called + *@param self | Provider struct + */ + function construct(Provider storage self) internal { + self.isRunning = true; + self.committedBlock = uint32(block.number); + self.receiptList.length = 1; // trick to show mapped index(0)'s values as zero + } + + function push( + LL storage self, + uint256 next, + uint32 endpoint, + int32 core, + uint32 idx + ) internal { + self.items[idx].next = uint32(next); + self.items[idx].endpoint = endpoint; + self.items[idx].core = core; + } + + function _recursive(Interval storage self) + internal + view + returns ( + uint32, + uint32, + int32 + ) + { + return (self.next, self.endpoint, self.core); + } + + function _iterate( + LL storage self, + uint256 currentNodeIndex, + uint256 prevNodeIndex, + uint32 _length, + int32 carriedSum, + Lib.IntervalArg memory _interval + ) internal returns (bool) { + // int32 carriedSum = core; // Carried sum variable is assigned with job's given core number + uint256 currentNodeEndpoint = self.items[currentNodeIndex].endpoint; // read from the storage + int32 currentNodeCore = self.items[currentNodeIndex].core; + uint32 currentNodeNext = self.items[currentNodeIndex].next; + do { + if (_interval.startTimestamp >= currentNodeEndpoint) { + if (_interval.startTimestamp == currentNodeEndpoint) { + int32 temp = currentNodeCore + (_interval.core * -1); + if (temp == 0) { + self.items[prevNodeIndex].next = currentNodeNext; + delete self.items[currentNodeIndex]; // length of the list is not incremented + self.length = _length; // !!! !!!! !!! + } else { + if (temp > _interval.availableCore) return false; + self.items[currentNodeIndex].core = temp; + self.length = _length; + } + return true; + } else { + /* Covers [val, val1) s = s-1 */ + push(self, self.items[prevNodeIndex].next, _interval.startTimestamp, _interval.core * -1, _length); + self.items[prevNodeIndex].next = _length; + self.length = _length + 1; + return true; + } + } + /* Inside while loop carriedSum is updated. If enters into if statement it + means revert() is catched and all the previous operations are reverted back */ + carriedSum += currentNodeCore; + if (carriedSum > _interval.availableCore) { + return false; + } + prevNodeIndex = currentNodeIndex; + currentNodeIndex = currentNodeNext; // already in the memory, cheaper + currentNodeEndpoint = self.items[currentNodeIndex].endpoint; // read from the storage + currentNodeCore = self.items[currentNodeIndex].core; + currentNodeNext = self.items[currentNodeIndex].next; + } while (true); + } + + function _iterateStart( + LL storage self, + uint256 prevNodeIndex, + uint256 currentNodeIndex, + Lib.IntervalArg memory _interval + ) + internal + returns ( + uint256 flag, + uint256 _addr, + uint256, + int32 carriedSum, + uint32 prevNodeIndexNextTemp, + int32 updatedCoreVal + ) + { + flag = 1; + uint32 currentNodeEndpoint = self.items[prevNodeIndex].endpoint; + uint32 currentNodeNext = self.items[prevNodeIndex].next; + int32 currentNodeCore = self.items[prevNodeIndex].core; + int32 temp; + do { + /* Inside while loop carriedSum is updated. If enters into if statement it + means revert() is catched and all the previous operations are reverted back */ + if (_interval.endTimestamp >= currentNodeEndpoint) { + carriedSum += _interval.core; + if (carriedSum > _interval.availableCore) return (0, _addr, 0, 0, 0, 0); + + if (_interval.endTimestamp == currentNodeEndpoint) { + temp = currentNodeCore + int32(_interval.core); + carriedSum += currentNodeCore; + if (carriedSum > _interval.availableCore || temp > _interval.availableCore) return (0, _addr, 0, 0, 0, 0); + + if (temp != 0) { + flag = 2; // helps to prevent pushing since it is already added + } else { + flag = 3; // helps to prevent pushing since it is already added + prevNodeIndexNextTemp = self.items[prevNodeIndex].next; + self.items[currentNodeIndex] = self.items[currentNodeNext]; + } + } + _addr = currentNodeIndex; /* "addr" points the index to be added into the linked list */ + return (flag, _addr, prevNodeIndex, carriedSum, prevNodeIndexNextTemp, temp); + } + carriedSum += currentNodeCore; + if (carriedSum > _interval.availableCore) { + return (0, _addr, 0, 0, 0, 0); + } + prevNodeIndex = currentNodeIndex; + currentNodeIndex = currentNodeNext; + currentNodeEndpoint = self.items[currentNodeIndex].endpoint; + currentNodeNext = self.items[currentNodeIndex].next; + currentNodeCore = self.items[currentNodeIndex].core; + } while (true); + } + + function _refund( + Status storage self, + address provider, + uint32 jobID, + uint256[] memory core, + uint256[] memory elapsedTime + ) internal returns (uint256 flag) { + require(self.jobInfo == keccak256(abi.encodePacked(core, elapsedTime))); + Job storage job = self.jobs[jobID]; + require( + (msg.sender == self.jobOwner || msg.sender == provider) && + job.stateCode != Lib.JobStateCodes.COMPLETED && + job.stateCode != Lib.JobStateCodes.REFUNDED && + job.stateCode != Lib.JobStateCodes.CANCELLED + ); + } + + function overlapCheck(LL storage self, IntervalArg memory _interval) internal returns (uint256 flag) { + uint256 addr = self.tail; + uint256 addrTemp; + uint256 prevNodeIndex; + uint256 currentNodeIndex; + uint256 prevNodeIndexNextTemp; + int32 carriedSum; + int32 updatedCoreVal; + if (_interval.endTimestamp <= self.items[addr].endpoint) { + /* Current node points index of previous tail-node right after the insert operation */ + currentNodeIndex = prevNodeIndex = addr; + (flag, addr, prevNodeIndex, carriedSum, prevNodeIndexNextTemp, updatedCoreVal) = _iterateStart( + self, + prevNodeIndex, + currentNodeIndex, + _interval + ); + if (flag == 0) { + return 0; // false + } + } + uint256 _length = self.length; + if (flag <= 1) { + /* inserted while keeps sorted order */ + push(self, addr, _interval.endTimestamp, _interval.core, uint32(_length)); + _length += 1; + carriedSum = _interval.core; + if (flag == 0) { + addrTemp = addr; + prevNodeIndex = self.tail = uint32(_length - 1); + } else { + addrTemp = self.items[prevNodeIndex].next; + self.items[prevNodeIndex].next = uint32(_length - 1); + prevNodeIndex = uint32(_length - 1); + } + } + if (flag > 1) { + addrTemp = self.items[prevNodeIndex].next; + } + currentNodeIndex = addrTemp; //self.items[prevNodeIndex].next; + if (_iterate(self, currentNodeIndex, prevNodeIndex, uint32(_length), carriedSum, _interval)) { + if (flag == 2) { + self.items[addr].core = updatedCoreVal; + } + return 1; // true + } else { + if (flag <= 1) { + delete self.items[uint32(_length - 1)]; + if (prevNodeIndex == self.tail) self.tail = uint32(addrTemp); + else self.items[prevNodeIndex].next = uint32(addrTemp); // change on the contract storage + } else if (flag == 3) { + self.items[prevNodeIndex].next = uint32(prevNodeIndexNextTemp); + } + return 0; // false + } + } + + /* used for test getReceiptListSize */ + function printIndex(LL storage self, uint32 index) + external + view + returns ( + uint32, + uint256 idx, + int32 + ) + { + idx = self.tail; + for (uint256 i = 0; i < index; i++) { + idx = self.items[idx].next; + } + // self.length: receipt_list_size + return (self.length, self.items[idx].endpoint, self.items[idx].core); + } +} diff --git a/contracts/SafeMath.sol b/contracts/SafeMath.sol index 38ca609..b27a447 100644 --- a/contracts/SafeMath.sol +++ b/contracts/SafeMath.sol @@ -1,29 +1,138 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +/** + * Taken from https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "subtraction overflow"); + uint256 c = a - b; + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + uint256 c = a * b; + require(c / a == b, "multiplication overflow"); + return c; + } - function add(uint a, uint b) internal pure returns (uint c) { - c = a + b; - require(c >= a); + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, "division by zero"); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } - function sub(uint a, uint b) internal pure returns (uint c) { - require(b <= a); - c = a - b; + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0, "modulo by zero"); + return a % b; + } +} + +library SafeMath32 { + function add(uint32 _a, uint32 _b) internal pure returns (uint32) { + uint32 c = _a + _b; + require(c >= _a); return c; } - function mul(uint a, uint b) internal pure returns (uint c) { - c = a * b; - require(a == 0 || c / a == b); + function sub(uint32 _a, uint32 _b) internal pure returns (uint32) { + require(_b <= _a); + uint32 c = _a - _b; return c; } - function div(uint a, uint b) internal pure returns (uint c) { - require(b > 0); - c = a / b; + function mul(uint32 _a, uint32 _b) internal pure returns (uint32) { + if (_a == 0) { + return 0; + } + uint32 c = _a * _b; + require(c / _a == _b); return c; } + /* + function div(uint32 _a, uint32 _b) internal pure returns (uint32) { + require(_b > 0); // Solidity only automatically asserts when dividing by 0 + uint32 c = _a / _b; + // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold + + return c; + } + */ } diff --git a/contracts/eBlocBroker.sol b/contracts/eBlocBroker.sol new file mode 100644 index 0000000..525dc93 --- /dev/null +++ b/contracts/eBlocBroker.sol @@ -0,0 +1,1117 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./SafeMath.sol"; +import "./Lib.sol"; +import "./eBlocBrokerInterface.sol"; +import "./EBlocBrokerBase.sol"; +import "./ERC20/ERC20.sol"; + +/** + @title eBlocBroker + @author Alper Alimoglu - @avatar-lavventura + @author email: alper.alimoglu AT gmail.com + @dev + The eBlocBroker is a blockchain based autonomous computational resource broker. + + Expands upon the ERC20 token standard + https://theethereum.wiki/w/index.php/ERC20_Token_Standard + */ +contract eBlocBroker is + eBlocBrokerInterface, + EBlocBrokerBase +{ + using SafeMath for uint256; + using SafeMath32 for uint32; + // using SafeMath64 for uint64; + + using Lib for Lib.CloudStorageID; + using Lib for Lib.IntervalArg; + using Lib for Lib.JobArgument; + using Lib for Lib.JobIndexes; + using Lib for Lib.JobStateCodes; + using Lib for Lib.LL; + using Lib for Lib.Provider; + using Lib for Lib.ProviderInfo; + using Lib for Lib.Status; + using Lib for Lib.HashToROC; + + /** + * @dev eBlocBroker constructor that sets the original `owner` of the + * contract to the msg.sender and minting. + */ + constructor(address _tokenAddress) { + tokenAddress = _tokenAddress; + ebb_owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == ebb_owner); // dev: Sender must be owner + _; + } + + function getOwner() public view virtual returns (address) { + return ebb_owner; + } + + /** + @dev + * Following function is a general-purpose mechanism for performing payment withdrawal + * by the provider provider and paying of unused core, cache, and dataTransfer usage cost + * back to the client + * @param key Uniqu ID for the given job. + * @param args The index of the job and ID of the job to identify for workflow {index, jobID, endTimestamp}. + * => elapsedTime Execution time in minutes of the completed job. + * @param resultIpfsHash Ipfs hash of the generated output files. + */ + function processPayment( + string memory key, + Lib.JobIndexes memory args, + bytes32 resultIpfsHash + ) public whenProviderRunning { + require(args.endTimestamp <= block.timestamp, "Ahead now"); + /* If "msg.sender" is not mapped on 'provider' struct or its "key" and "index" + is not mapped to a job, this will throw automatically and revert all changes */ + Lib.Provider storage provider = providers[msg.sender]; + Lib.Status storage jobInfo = provider.jobStatus[key][args.index]; + require(jobInfo.jobInfo == keccak256(abi.encodePacked(args.core, args.runTime))); + Lib.Job storage job = jobInfo.jobs[args.jobID]; /* used as a pointer to a storage */ + if (job.stateCode == Lib.JobStateCodes.COMPLETED){ + return; + } + //: job should be in running state if positive execution duration is provided + require(job.stateCode == Lib.JobStateCodes.RUNNING, "HERE_1"); + //: provider cannot request more execution time of the job that is already requested + require(args.elapsedTime > 0 && args.elapsedTime <= args.runTime[args.jobID], "HERE_2"); + //: provider cannot request more than the job's given dataTransferIn + require(args.dataTransferIn <= jobInfo.dataTransferIn, "HERE_3"); + //: provider cannot request more than the job's given dataTransferOut + require(args.dataTransferOut <= jobInfo.dataTransferOut, "HERE_4"); + Lib.ProviderInfo memory info = provider.info[jobInfo.pricesSetBlockNum]; + uint256 gain; + uint256 _refund; + uint256 core = args.core[args.jobID]; + uint256 runTime = args.runTime[args.jobID]; + if (args.finalize == 0 || args.finalize == 3) { + // SINGLE-JOB or BEGIN-JOB of the workflow + if (jobInfo.cacheCost > 0) { + //: checking data transferring cost + gain = info.priceCache.mul(args.dataTransferIn); // cache payment to receive + if (jobInfo.cacheCost > gain) { + _refund = jobInfo.cacheCost - gain; + } else { + gain = jobInfo.cacheCost; + } + delete jobInfo.cacheCost; + } + + if (jobInfo.dataTransferIn > 0 && args.dataTransferIn != jobInfo.dataTransferIn) { + // check data transferring cost + //: data transfer refund + _refund = _refund.add(info.priceDataTransfer.mul((jobInfo.dataTransferIn.sub(args.dataTransferIn)))); + // prevents additional cacheCost to be requested + delete jobInfo.dataTransferIn; + } + } + + if (args.finalize == 2 || args.finalize == 3) { + if (jobInfo.dataTransferOut > 0 && args.dataTransferOut != jobInfo.dataTransferOut) { + _refund = _refund.add(info.priceDataTransfer.mul(jobInfo.dataTransferOut.sub(args.dataTransferOut))); + if (jobInfo.cacheCost > 0) { + // If job cache is not used full refund for cache + _refund = _refund.add(jobInfo.cacheCost); // cacheCost for storage is already multiplied with priceCache + delete jobInfo.cacheCost; + } + if (jobInfo.dataTransferIn > 0 && args.dataTransferIn == 0) { + // If job data transfer is not used full refund for cache + _refund = _refund.add(info.priceDataTransfer.mul(jobInfo.dataTransferIn)); + delete jobInfo.dataTransferIn; + } + } + } + gain = gain.add( + uint256(info.priceCoreMin).mul(core.mul(args.elapsedTime)).add( // computationalCost + info.priceDataTransfer.mul((args.dataTransferIn.add(args.dataTransferOut))) // dataTransferCost + ) + ); + gain = gain.add(jobInfo.receivedRegisteredDataFee); + //: computationalCostRefund + _refund = _refund.add(info.priceCoreMin.mul(core.mul((runTime.sub(args.elapsedTime))))); + require(gain.add(_refund) <= jobInfo.received, "gain.add(refund) > received"); + Lib.IntervalArg memory _interval; + _interval.startTimestamp = job.startTimestamp; + _interval.endTimestamp = uint32(args.endTimestamp); + _interval.availableCore = int32(info.availableCore); + _interval.core = int32(int256(core)); // int32(core); + if (provider.receiptList.overlapCheck(_interval) == 0) { + // Important to check already refunded job or not, prevents double spending + job.stateCode = Lib.JobStateCodes.REFUNDED; + _refund = jobInfo.received; + _refund.add(jobInfo.receivedRegisteredDataFee); + jobInfo.received = 0; + jobInfo.receivedRegisteredDataFee = 0; + // pay back newOwned(jobInfo.received) back to the requester, which is full refund + // _distributeTransfer(jobInfo.jobOwner, _refund); + ERC20(tokenAddress).increaseAllowance(jobInfo.jobOwner, _refund); + _logProcessPayment(key, args, resultIpfsHash, jobInfo.jobOwner, 0, _refund); + return; + } + if (job.stateCode == Lib.JobStateCodes.CANCELLED) { + // prevents double spending used as a reentrancy guard + job.stateCode = Lib.JobStateCodes.REFUNDED; + } else { + // prevents double spending used as a reentrancy guard + job.stateCode = Lib.JobStateCodes.COMPLETED; + } + // jobInfo.received = jobInfo.received.sub(gain.add(_refund)); + jobInfo.receivedRegisteredDataFee = 0; + if (_refund > 0) { + // unused core and bandwidth is refunded back to the client + ERC20(tokenAddress).increaseAllowance(jobInfo.jobOwner, _refund); + } + ERC20(tokenAddress).increaseAllowance(msg.sender, gain); + _logProcessPayment(key, args, resultIpfsHash, jobInfo.jobOwner, gain, _refund); + return; + } + + /** + * @dev Refund funds the complete amount to client if requested job is still + * in the pending state or is not completed one hour after its required + * time. If the job is in the running state, it triggers LogRefund event on + * the blockchain, which will be caught by the provider in order to cancel + * the job. + * + * @param provider Ethereum Address of the provider. + * @param key Uniqu ID for the given job. + * @param index The index of the job. + * @param jobID ID of the job to identify under workflow. + * @return bool + */ + function refund( + address provider, + string memory key, + uint32 index, + uint32 jobID, + uint256[] memory core, + uint256[] memory elapsedTime + ) public returns (bool) { + Lib.Provider storage _provider = providers[provider]; + /* + If 'provider' is not mapped on '_provider' map or its 'key' and 'index' + is not mapped to a job , this will throw automatically and revert all changes + */ + _provider.jobStatus[key][index]._refund(provider, jobID, core, elapsedTime); ///////////// + Lib.Status storage jobInfo = _provider.jobStatus[key][index]; + /* require(jobInfo.jobInfo == keccak256(abi.encodePacked(core, elapsedTime))); */ + Lib.Job storage job = jobInfo.jobs[jobID]; + /* require( */ + /* (msg.sender == jobInfo.jobOwner || msg.sender == provider) && */ + /* job.stateCode != Lib.JobStateCodes.COMPLETED && */ + /* job.stateCode != Lib.JobStateCodes.REFUNDED && */ + /* job.stateCode != Lib.JobStateCodes.CANCELLED */ + /* ); */ + uint256 amount; + if ( + !_provider.isRunning || + job.stateCode <= Lib.JobStateCodes.PENDING || // If job' state is SUBMITTED(0) or PENDING(1) + (job.stateCode == Lib.JobStateCodes.RUNNING && + (block.timestamp - job.startTimestamp) > elapsedTime[jobID] * 60 + 1 hours) + ) { + // job.stateCode remain in running state after one hour that job should have finished + job.stateCode = Lib.JobStateCodes.REFUNDED; /* Prevents double spending and re-entrancy attack */ + amount = jobInfo.received.add(jobInfo.receivedRegisteredDataFee); + //: balance is zeroed out before the transfer + jobInfo.received = 0; + jobInfo.receivedRegisteredDataFee = 0; + // _distributeTransfer(jobInfo.jobOwner, amount); // transfer cost to job owner + ERC20(tokenAddress).increaseAllowance(jobInfo.jobOwner, amount); + } else if (job.stateCode == Lib.JobStateCodes.RUNNING) { + job.stateCode = Lib.JobStateCodes.CANCELLED; + } else { + revert(); + } + emit LogRefundRequest(provider, key, index, jobID, amount); /* scancel log */ + return true; + } + + function refundStorageDeposit( + address provider, + address payable requester, + bytes32 sourceCodeHash + ) public returns (bool) { + Lib.Provider storage _provider = providers[provider]; + Lib.Storage storage storageInfo = _provider.storageInfo[requester][sourceCodeHash]; + uint256 payment = storageInfo.received; + storageInfo.received = 0; + require(payment > 0 && !_provider.jobSt[sourceCodeHash].isVerifiedUsed); + Lib.JobStorage storage jobSt = _provider.jobSt[sourceCodeHash]; + // required remaining time to cache should be 0 + require(jobSt.receivedBlock.add(jobSt.storageDuration) < block.number); + _cleanJobStorage(jobSt); + // _distributeTransfer(requester, payment); + ERC20(tokenAddress).increaseAllowance(requester, payment); + emit LogDepositStorage(requester, payment); + return true; + } + + function depositStorage(address dataOwner, bytes32 sourceCodeHash) public whenProviderRunning returns (bool) { + Lib.Provider storage provider = providers[msg.sender]; + Lib.Storage storage storageInfo = provider.storageInfo[dataOwner][sourceCodeHash]; + Lib.JobStorage storage jobSt = provider.jobSt[sourceCodeHash]; + require(jobSt.isVerifiedUsed && jobSt.receivedBlock.add(jobSt.storageDuration) < block.number); + uint256 payment = storageInfo.received; + storageInfo.received = 0; + // _distributeTransfer(msg.sender, payment); + ERC20(tokenAddress).increaseAllowance(msg.sender, payment); + _cleanJobStorage(jobSt); + emit LogDepositStorage(msg.sender, payment); + return true; + } + + /** + * @dev Register a provider's (msg.sender's) given information + * + * @param gpgFingerprint is a bytes8 containing a gpg key ID that is used by GNU + Privacy Guard to encrypt or decrypt files. + * @param gmail is a string containing an gmail + * @param fcID is a string containing a Federated Cloud ID for + sharing requester's repository with the provider through B2DROP. + * @param availableCore is a uint32 value containing the number of available + cores. + * @param prices is a structure containing four uint32 values, which are + * => price per core-minute, + * => price per megabyte of transferring data, + * => price per megabyte of storage usage for an hour, and + * => price per megabyte of cache usage values respectively. + * @param commitmentBlockDur is a uint32 value containing the duration + of the committed prices. + * @param ipfsAddress is a string containing an IPFS peer ID for creating peer + connection between requester and provider. + * @return bool + */ + function registerProvider( + bytes32 gpgFingerprint, + string memory gmail, + string memory fcID, + string memory ipfsAddress, + uint32 availableCore, + uint32[] memory prices, + uint32 commitmentBlockDur + ) public whenProviderNotRegistered returns (bool) { + Lib.Provider storage provider = providers[msg.sender]; + require( + availableCore > 0 && + prices[0] > 0 && + // price per storage should be minimum 1, which helps to identify + // is user used or not the related data file + prices[2] > 0 && + !provider.isRunning && + // commitment duration should be minimum 1 hour + commitmentBlockDur >= ONE_HOUR_BLOCK_DURATION + ); + _setProviderPrices(provider, block.number, availableCore, prices, commitmentBlockDur); + pricesSetBlockNum[msg.sender].push(uint32(block.number)); + provider.construct(); + registeredProviders.push(msg.sender); + emit LogProviderInfo(msg.sender, gpgFingerprint, gmail, fcID, ipfsAddress); + return true; + } + + function updateProviderInfo( + bytes32 gpgFingerprint, + string memory gmail, + string memory fcID, + string memory ipfsAddress + ) public whenProviderRegistered returns (bool) { + emit LogProviderInfo(msg.sender, gpgFingerprint, gmail, fcID, ipfsAddress); + return true; + } + + function setDataVerified(bytes32[] memory sourceCodeHash) public whenProviderRunning returns (bool) { + Lib.Provider storage provider = providers[msg.sender]; + for (uint256 i = 0; i < sourceCodeHash.length; i++) { + bytes32 codeHash = sourceCodeHash[i]; + if (_updateDataReceivedBlock(provider, codeHash)) { + provider.jobSt[codeHash].isVerifiedUsed = true; + } + } + return true; + } + + function setDataPublic( + string memory key, + uint32 index, + bytes32[] memory sourceCodeHash, + uint8[] memory cacheType + ) public whenProviderRunning { + Lib.Provider storage provider = providers[msg.sender]; + // List of provide sourceCodeHash should be same as with the ones that + // are provided along with the job + require(provider.jobStatus[key][index].sourceCodeHash == keccak256(abi.encodePacked(sourceCodeHash, cacheType))); + for (uint256 i = 0; i < sourceCodeHash.length; i++) { + Lib.JobStorage storage jobSt = provider.jobSt[sourceCodeHash[i]]; + if (jobSt.isVerifiedUsed && cacheType[i] == uint8(Lib.CacheID.PUBLIC)) { + jobSt.isPrivate = false; + } + } + } + + /** + * @dev Update prices and available core number of the provider + * + * @param availableCore Available core number. + * @param commitmentBlockDur Requred block number duration for prices + * to committed. + * @param prices Array of prices as array ([priceCoreMin, priceDataTransfer, + * priceStorage, priceCache]) to update for the provider. + * @return bool + */ + function updateProviderPrices( + uint32 availableCore, + uint32 commitmentBlockDur, + uint32[] memory prices + ) public whenProviderRegistered returns (bool) { + require(availableCore > 0 && prices[0] > 0 && commitmentBlockDur >= ONE_HOUR_BLOCK_DURATION); + Lib.Provider storage provider = providers[msg.sender]; + uint32[] memory providerInfo = pricesSetBlockNum[msg.sender]; + uint32 pricesSetBn = providerInfo[providerInfo.length - 1]; + if (pricesSetBn > block.number) { + // enters if already updated futher away of the committed block on the same block + _setProviderPrices(provider, pricesSetBn, availableCore, prices, commitmentBlockDur); + } else { + uint256 _commitmentBlockDur = provider.info[pricesSetBn].commitmentBlockDur; + uint256 committedBlock = pricesSetBn + _commitmentBlockDur; // future block number + if (committedBlock <= block.number) { + committedBlock = (block.number - pricesSetBn) / _commitmentBlockDur + 1; + // next price cycle to be considered + committedBlock = pricesSetBn + committedBlock * _commitmentBlockDur; + } + _setProviderPrices(provider, committedBlock, availableCore, prices, commitmentBlockDur); + pricesSetBlockNum[msg.sender].push(uint32(committedBlock)); + } + return true; + } + + /** + * @dev Suspend provider as it will not receive any more job, which may + * only be performed only by the provider owner. Suspends the access + * to the provider. Only provider owner could stop it. + */ + function suspendProvider() public whenProviderRunning returns (bool) { + providers[msg.sender].isRunning = false; // provider will not accept any jobs + return true; + } + + /** + * @dev Resume provider as it will continue to receive jobs, which may + * only be performed only by the provider owner. + */ + function resumeProvider() public whenProviderRegistered whenProviderSuspended returns (bool) { + providers[msg.sender].isRunning = true; // provider will start accept jobs + return true; + } + + function hashToROC(bytes32 hash, uint32 roc, bool isIPFS) public whenProviderRegistered returns (bool) { + providers[msg.sender].hashToROC[hash] = roc; + emit LogHashROC(msg.sender, hash, roc, isIPFS); + return true; + } + + /** + * @dev Register or update a requester's (msg.sender's) information to + * eBlocBroker. + * + * @param gpgFingerprint | is a bytes8 containing a gpg key ID that is used by the + GNU Privacy Guard to encrypt or decrypt files. + * @param gmail is a string containing an gmail + * @param fcID is a string containing a Federated Cloud ID for + sharing requester's repository with the provider through B2DROP. + * @param ipfsAddress | is a string containing an IPFS peer ID for creating peer + connection between requester and provider. + * @return bool + */ + function registerRequester( + bytes32 gpgFingerprint, + string memory gmail, + string memory fcID, + string memory ipfsAddress + ) public returns (bool) { + requesterCommittedBlock[msg.sender] = uint32(block.number); + emit LogRequester(msg.sender, gpgFingerprint, gmail, fcID, ipfsAddress); + return true; + } + + /** + * @dev Register a given data's sourceCodeHash by the cluster + * + * @param sourceCodeHash source code hash of the provided data + * @param price Price in Gwei of the data + * @param commitmentBlockDur | Commitment duration of the given price + in block duration + */ + function registerData( + bytes32 sourceCodeHash, + uint32 price, + uint32 commitmentBlockDur + ) public whenProviderRegistered { + Lib.RegisteredData storage registeredData = providers[msg.sender].registeredData[sourceCodeHash]; + require( + registeredData.committedBlock.length == 0 && // in order to register, is shouldn't be already registered + commitmentBlockDur >= ONE_HOUR_BLOCK_DURATION + ); + + /* Always increment price of the data by 1 before storing it. By default + if price == 0, data does not exist. If price == 1, it's an existing + data that costs nothing. If price > 1, it's an existing data that + costs the given price. */ + if (price == 0) { + price = price + 1; + } + registeredData.dataInfo[block.number].price = price; + registeredData.dataInfo[block.number].commitmentBlockDur = commitmentBlockDur; + registeredData.committedBlock.push(uint32(block.number)); + emit LogRegisterData(msg.sender, sourceCodeHash); + } + + /** + * @dev Register a given data's sourceCodeHash removal by the cluster + * + * @param sourceCodeHash: source code hashe of the already registered data + */ + function removeRegisteredData(bytes32 sourceCodeHash) public whenProviderRegistered { + delete providers[msg.sender].registeredData[sourceCodeHash]; + } + + /** + * @dev Update a given data's prices registiration by the cluster + * + * @param sourceCodeHash: Source code hashe of the provided data + * @param price: Price in Gwei of the data + * @param commitmentBlockDur: Commitment duration of the given price + in block duration + */ + function updataDataPrice( + bytes32 sourceCodeHash, + uint32 price, + uint32 commitmentBlockDur + ) public whenProviderRegistered { + Lib.RegisteredData storage registeredData = providers[msg.sender].registeredData[sourceCodeHash]; + require(registeredData.committedBlock.length > 0); + if (price == 0) { + price = price + 1; + } + uint32[] memory committedBlockList = registeredData.committedBlock; + uint32 pricesSetBn = committedBlockList[committedBlockList.length - 1]; + if (pricesSetBn > block.number) { + // enters if already updated futher away of the committed block on the commitment duration + registeredData.dataInfo[pricesSetBn].price = price; + registeredData.dataInfo[pricesSetBn].commitmentBlockDur = commitmentBlockDur; + } else { + uint256 _commitmentBlockDur = registeredData.dataInfo[pricesSetBn].commitmentBlockDur; + uint256 committedBlock = pricesSetBn + _commitmentBlockDur; // future block number + if (committedBlock <= block.number) { + committedBlock = (block.number - pricesSetBn) / _commitmentBlockDur + 1; + committedBlock = pricesSetBn + committedBlock * _commitmentBlockDur; + } + registeredData.dataInfo[committedBlock].price = price; + registeredData.dataInfo[committedBlock].commitmentBlockDur = commitmentBlockDur; + registeredData.committedBlock.push(uint32(committedBlock)); + } + } + + /** + * @dev Perform a job submission through eBlocBroker by a requester. + This deposit is locked in the contract until the job is finalized or cancelled. + * + * @param key Contains a unique name for the requester’s job. + * @param dataTransferIn An array of uint32 values that denote the amount of + data transfer to be made in megabytes in order to download the + requester’s data from cloud storage into provider’s local storage. + * @param args is a structure containing additional values as follows: + * => provider is an Ethereum address value containing the Ethereum address + of the provider that is requested to run the job. + * => cloudStorageID | An array of uint8 values that denote whether the + requester’s data is stored and shared using either IPFS, B2DROP, IPFS + (with GNU Privacy Guard encryption), or Google Drive. + * => cacheType An array of uint8 values that denote whether the requester’s + data will be cached privately within job owner's home directory, or + publicly for other requesters' access within a shared directory for + all the requesters. + * => core An array of uint16 values containing the number of cores + requested to run the workflow with respect to each of the + corresponding job. + * => The runTime argument is an array of uint32 values containing the + expected run time in minutes to run the workflow regarding each of the + corresponding jobs. + * => priceBlockIndex The uint32 value containing the block number + when the requested provider set its prices most recent. + * => dataPricfesSetBlockNum An array of uint32 values that denote whether + the provider’s registered data will be used or not. If it is zero, + then requester’s own data will be considered, which that is cached or + downloaded from the cloud storage will be considered. Otherwise it + should be the block number when the requested provider’s registered + data’s price is set most recent corresponding to each of the + sourceCodeHash. + * => dataTransferOut Value denoting the amount of data transfer required in + megabytes to upload output files generated by the requester’s job into + cloud storage. + * @param storageDuration An array of uint32 values that denote the duration + it will take in hours to cache the downloaded data of the received + job. + * @param sourceCodeHash An array of bytes32 values that are MD5 hashes with + respect to each of the corresponding source code and data files. + */ + function submitJob( + string memory key, + uint32[] memory dataTransferIn, + Lib.JobArgument memory args, + uint32[] memory storageDuration, + bytes32[] memory sourceCodeHash + ) public payable { + Lib.Provider storage provider = providers[args.provider]; + require( + provider.isRunning && + sourceCodeHash.length > 0 && + storageDuration.length == args.dataPricesSetBlockNum.length && + storageDuration.length == sourceCodeHash.length && + storageDuration.length == dataTransferIn.length && + storageDuration.length == args.cloudStorageID.length && + storageDuration.length == args.cacheType.length && + args.cloudStorageID[0] <= 4 && + args.core.length == args.runTime.length && + doesRequesterExist(msg.sender) && + bytes(key).length <= 64 && + orcID[msg.sender].length > 0 && + orcID[args.provider].length > 0 + ); + if (args.cloudStorageID.length > 0) + for (uint256 i = 1; i < args.cloudStorageID.length; i++) + require( + args.cloudStorageID[0] == args.cloudStorageID[i] || args.cloudStorageID[i] <= uint8(Lib.CloudStorageID.NONE) + // IPFS or IPFS_GPG or NONE + ); + + uint32[] memory providerInfo = pricesSetBlockNum[args.provider]; + uint256 priceBlockIndex = providerInfo[providerInfo.length - 1]; + if (priceBlockIndex > block.number) { + // if the provider's price is updated on the future block enter + priceBlockIndex = providerInfo[providerInfo.length - 2]; + } + require(args.priceBlockIndex == priceBlockIndex); + Lib.ProviderInfo memory info = provider.info[priceBlockIndex]; + uint256 cost; + uint256[3] memory tmp; + /* uint256 storageCost; */ + // uint256 refunded; + // "storageDuration[0]" => As temp variable stores the calcualted cacheCost + // "dataTransferIn[0]" => As temp variable stores the overall dataTransferIn value, + // decreased if there is caching for specific block + // refunded => used as receivedRegisteredDataFee due to limit for local variables + + // sum, _dataTransferIn, storageCost, cacheCost, sumRegisteredDataDeposit + // | | | / ___/ + // / | | / | + (cost, dataTransferIn[0], tmp[0], tmp[1], tmp[2]) = _calculateCacheCost( + provider, + args, + sourceCodeHash, + dataTransferIn, + storageDuration, + info + ); + cost = cost.add(_calculateComputingCost(info, args.core, args.runTime)); + + // @args.jobPrice: paid, @cost: calculated + require(args.jobPrice >= cost); + /* transfer(getOwner(), cost); // transfer cost to contract */ + ERC20(tokenAddress).transferFrom(msg.sender, address(this), cost); + // here returned "priceBlockIndex" used as temp variable to hold pushed index value of the jobStatus struct + Lib.Status storage jobInfo = provider.jobStatus[key].push(); + jobInfo.cacheCost = tmp[1]; + jobInfo.dataTransferIn = dataTransferIn[0]; + jobInfo.dataTransferOut = args.dataTransferOut; + jobInfo.pricesSetBlockNum = uint32(priceBlockIndex); + jobInfo.received = cost.sub(tmp[0]); + jobInfo.jobOwner = payable(msg.sender); + jobInfo.sourceCodeHash = keccak256(abi.encodePacked(sourceCodeHash, args.cacheType)); + jobInfo.jobInfo = keccak256(abi.encodePacked(args.core, args.runTime)); + jobInfo.receivedRegisteredDataFee = tmp[2]; + priceBlockIndex = provider.jobStatus[key].length - 1; + emitLogJob(key, uint32(priceBlockIndex), sourceCodeHash, args, cost); + return; + } + + /* @dev Set the job's state (stateCode) which is obtained from Slurm */ + function setJobState(Lib.Job storage job, Lib.JobStateCodes stateCode) internal validJobStateCode(stateCode) returns (bool) { + job.stateCode = stateCode; + return true; + } + + /* function setJobStatePending( */ + /* string memory key, */ + /* uint32 index, */ + /* uint32 jobID */ + /* ) public returns (bool) { */ + /* Lib.Job storage job = providers[msg.sender].jobStatus[key][index].jobs[jobID]; */ + /* // job.stateCode should be {SUBMITTED (0)} */ + /* require(job.stateCode == Lib.JobStateCodes.SUBMITTED, "Not permitted"); */ + /* job.stateCode = Lib.JobStateCodes.PENDING; */ + /* emit LogSetJob(msg.sender, key, index, jobID, uint8(Lib.JobStateCodes.PENDING)); */ + /* } */ + + function setJobStateRunning( + string memory key, + uint32 index, + uint32 jobID, + uint32 startTimestamp + ) public whenBehindNow(startTimestamp) returns (bool) { + /* Used as a pointer to a storage */ + Lib.Job storage job = providers[msg.sender].jobStatus[key][index].jobs[jobID]; + /* Provider can sets job's status as RUNNING and its startTimestamp only one time + job.stateCode should be {SUBMITTED (0), PENDING(1)} */ + require(job.stateCode <= Lib.JobStateCodes.PENDING, "Not permitted"); + job.startTimestamp = startTimestamp; + job.stateCode = Lib.JobStateCodes.RUNNING; + emit LogSetJob(msg.sender, key, index, jobID, uint8(Lib.JobStateCodes.RUNNING)); + return true; + } + + function authenticateOrcID(address user, bytes32 orcid) public onlyOwner whenOrcidNotVerified(user) returns (bool) { + orcID[user] = orcid; + return true; + } + + /* -=-=-=-=-=-=-=-=-=-=- INTERNAL FUNCTIONS -=-=-=-=-=-=-=-=-=-=- */ + function _setProviderPrices( + Lib.Provider storage provider, + uint256 mapBlock, + uint32 availableCore, + uint32[] memory prices, + uint32 commitmentBlockDur + ) internal returns (bool) { + provider.info[mapBlock] = Lib.ProviderInfo({ + availableCore: availableCore, + priceCoreMin: prices[0], + priceDataTransfer: prices[1], + priceStorage: prices[2], + priceCache: prices[3], + commitmentBlockDur: commitmentBlockDur + }); + return true; + } + + /** + * @dev Update data's received block number with block number + * @param provider Structure of the provider + * @param sourceCodeHash hash of the requested data + */ + function _updateDataReceivedBlock(Lib.Provider storage provider, bytes32 sourceCodeHash) internal returns (bool) { + Lib.JobStorage storage jobSt = provider.jobSt[sourceCodeHash]; // only provider can update receied job only to itself + if (jobSt.receivedBlock.add(jobSt.storageDuration) < block.number) { + // required remaining time to cache should be 0 + return false; + } + // provider can only update the block.number + jobSt.receivedBlock = uint32(block.number) - 1; + return true; + } + + function _calculateComputingCost( + Lib.ProviderInfo memory info, + uint16[] memory core, + uint16[] memory runTime + ) internal pure returns (uint256 sum) { + uint256 sumRunTime; + for (uint256 i = 0; i < core.length; i++) { + uint256 computationalCost = uint256(info.priceCoreMin).mul(uint256(core[i]).mul(uint256(runTime[i]))); + sumRunTime = sumRunTime.add(runTime[i]); + require(core[i] <= info.availableCore && computationalCost > 0 && sumRunTime <= ONE_DAY); + sum = sum.add(computationalCost); + } + return sum; + } + + /** + * @dev Add registered data price to cacheCost if cloudStorageID is CloudStorageID.NONE and + * the used provider's registered data exists. + */ + function _checkRegisteredData(uint8 _cloudStorageID, Lib.RegisteredData storage data) internal view returns (uint32) { + if (_cloudStorageID == uint8(Lib.CloudStorageID.NONE) && data.committedBlock.length > 0) { + uint32[] memory dataCommittedBlocks = data.committedBlock; + uint32 dataPriceSetBlockNum = dataCommittedBlocks[dataCommittedBlocks.length - 1]; + if (dataPriceSetBlockNum > block.number) { + // obtain the committed prices before the block number + dataPriceSetBlockNum = dataCommittedBlocks[dataCommittedBlocks.length - 2]; + } + require(dataPriceSetBlockNum == dataPriceSetBlockNum); + uint32 price = data.dataInfo[dataPriceSetBlockNum].price; + if (price > 1) + // provider is already registered data-file with its own price + return data.dataInfo[dataPriceSetBlockNum].price; + else return 0; + } + return 0; + } + + function _calculateCacheCost( + Lib.Provider storage provider, + Lib.JobArgument memory args, + bytes32[] memory sourceCodeHash, + uint32[] memory dataTransferIn, + uint32[] memory storageDuration, + Lib.ProviderInfo memory info + ) + internal + returns ( + uint256 sum, + uint32 _dataTransferIn, + uint256 storageCost, + uint256 cacheCost, + uint256 temp + ) + { + for (uint256 i = 0; i < sourceCodeHash.length; i++) { + bytes32 codeHash = sourceCodeHash[i]; + Lib.JobStorage storage jobSt = provider.jobSt[codeHash]; + Lib.Storage storage storageInfo = provider.storageInfo[msg.sender][codeHash]; + //: temp used for the `_receivedForStorage` variable + temp = storageInfo.received; + if (temp > 0 && jobSt.receivedBlock + jobSt.storageDuration < block.number) { + storageInfo.received = 0; + address _provider = args.provider; + // _distributeTransfer(_provider, temp); + ERC20(tokenAddress).increaseAllowance(_provider, temp); + // balances[_provider] += temp; // refund storage deposit back to provider + _cleanJobStorage(jobSt); + emit LogDepositStorage(args.provider, temp); + } + + if ( + !(temp > 0 || + (jobSt.receivedBlock + jobSt.storageDuration >= block.number && !jobSt.isPrivate && jobSt.isVerifiedUsed)) + ) { + Lib.RegisteredData storage registeredData = provider.registeredData[codeHash]; + // temp used for returned bool value True or False + if (args.cloudStorageID[i] != uint8(Lib.CloudStorageID.NONE) && registeredData.committedBlock.length > 0) { + revert(); + } + temp = _checkRegisteredData(args.cloudStorageID[i], registeredData); + if (temp == 0) { + // if returned value of _checkRegisteredData is False move on to next condition + if (jobSt.receivedBlock + jobSt.storageDuration < block.number) { + if (storageDuration[i] > 0) { + jobSt.receivedBlock = uint32(block.number); + // Hour is converted into block time, 15 seconds of block time is + // fixed and set only one time till the storage time expires + jobSt.storageDuration = storageDuration[i].mul(ONE_HOUR_BLOCK_DURATION); + // temp used for storageCostTemp variable + temp = info.priceStorage.mul(dataTransferIn[i].mul(storageDuration[i])); + storageInfo.received = uint248(temp); + storageCost = storageCost.add(temp); + if (args.cacheType[i] == uint8(Lib.CacheID.PRIVATE)) { + jobSt.isPrivate = true; // Set by the data owner + } + } else { + cacheCost = cacheCost.add(info.priceCache.mul(dataTransferIn[i])); + } + } else if (storageInfo.received == 0 && jobSt.isPrivate == true) { + // data~n is stored (privatley or publicly) on the provider + // checks whether the user is owner of the data file + cacheCost = cacheCost.add(info.priceCache.mul(dataTransferIn[i])); + } + //: communication cost should be applied + _dataTransferIn = _dataTransferIn.add(dataTransferIn[i]); + // owner of the sourceCodeHash is also detected, first time usage + emit LogDataStorageRequest(args.provider, msg.sender, codeHash, storageInfo.received); + } else { + // priority is given to dataset fee + sum = sum.add(temp); // keeps track of deposit for dataset fees for data + emit LogRegisteredDataRequestToUse(args.provider, codeHash); + } + } + } // for-loop ended + uint256 sumRegisteredDataDeposit = sum; + // sum already contains the registered data cost fee + sum = sum.add(info.priceDataTransfer.mul(_dataTransferIn.add(args.dataTransferOut))); + sum = sum.add(storageCost).add(cacheCost); + return (sum, _dataTransferIn, storageCost, cacheCost, sumRegisteredDataDeposit); + } + + /** + * @dev Clean storage struct storage (JobStorage, Storage) corresponding mapped sourceCodeHash + */ + function _cleanJobStorage(Lib.JobStorage storage jobSt) internal { + delete jobSt.receivedBlock; + delete jobSt.storageDuration; + delete jobSt.isPrivate; + delete jobSt.isVerifiedUsed; + } + + function _logProcessPayment( + string memory key, + Lib.JobIndexes memory args, + bytes32 resultIpfsHash, + address recipient, + uint256 receivedCent, + uint256 refundedCent + ) internal { + emit LogProcessPayment( + msg.sender, + key, + args.index, + args.jobID, + args.elapsedTime, + recipient, + receivedCent, + refundedCent, + resultIpfsHash, + args.dataTransferIn, + args.dataTransferOut + ); + } + + function emitLogJob( + string memory key, + uint32 index, + bytes32[] memory codeHashes, + Lib.JobArgument memory args, + uint256 cost + ) internal { + emit LogJob( + args.provider, + msg.sender, + key, + index, + args.cloudStorageID, + codeHashes, + args.cacheType, + args.core, + args.runTime, + cost, + args.jobPrice - cost, // refunded + args.workflowId + ); + } + + /* ## PUBLIC GETTERS ## */ + /* Returns a list of registered/updated provider's registered data prices */ + function getRegisteredDataBlockNumbers(address provider, bytes32 codeHash) external view returns (uint32[] memory) { + return providers[provider].registeredData[codeHash].committedBlock; + } + + /** + * @dev Get registered data price of the provider. + * + * If `pricesSetBn` is 0, it will return the current price at the + * current block-number that is called + * If mappings does not valid, then it will return (0, 0) + */ + function getRegisteredDataPrice( + address provider, + bytes32 sourceCodeHash, + uint32 pricesSetBn + ) public view returns (Lib.DataInfo memory) { + Lib.RegisteredData storage registeredData = providers[provider].registeredData[sourceCodeHash]; + if (pricesSetBn == 0) { + uint32[] memory _dataPrices = registeredData.committedBlock; + pricesSetBn = _dataPrices[_dataPrices.length - 1]; + if (pricesSetBn > block.number) { + // Obtain the committed data price before the block.number + pricesSetBn = _dataPrices[_dataPrices.length - 2]; + } + } + return (registeredData.dataInfo[pricesSetBn]); + } + + function getOrcID(address user) public view returns (bytes32) { + return orcID[user]; + } + + /* @dev Return the enrolled requester's block number of the enrolled + requester, which points to the block that logs `LogRequester event. It + takes Ethereum address of the requester, which can be obtained by calling + LogRequester event. + */ + function getRequesterCommittmedBlock(address requester) public view returns (uint32) { + return requesterCommittedBlock[requester]; + } + + /* @dev Return the registered provider's information. It takes Ethereum + address of the provider, which can be obtained by calling + getProviderAddresses If the pricesSetBn is 0, then it will return + the current price at the current block-number that is called + */ + function getProviderInfo(address provider, uint32 pricesSetBn) public view returns (uint32, Lib.ProviderInfo memory) { + uint32[] memory providerInfo = pricesSetBlockNum[provider]; + + if (pricesSetBn == 0) { + pricesSetBn = providerInfo[providerInfo.length - 1]; + if (pricesSetBn > block.number) { + // Obtain the committed prices before the block number + pricesSetBn = providerInfo[providerInfo.length - 2]; + } + } + return (pricesSetBn, providers[provider].info[pricesSetBn]); + } + + /** + * @dev Return various information about the submitted job such as the hash + * of output files generated by IPFS, UNIX timestamp on job's start time, + * received Gwei value from the client etc. + * + */ + function getJobInfo( + address provider, + string memory key, + uint32 index, + uint256 jobID + ) + public + view + returns ( + Lib.Job memory, + uint256, + address, + uint256, + uint256, + uint256 + ) + { + Lib.Status storage jobInfo = providers[provider].jobStatus[key][index]; + // Lib.Job storage job = jobInfo.jobs[jobID]; + return ( + jobInfo.jobs[jobID], + jobInfo.received, + jobInfo.jobOwner, + jobInfo.dataTransferIn, + jobInfo.cacheCost, + jobInfo.dataTransferOut + ); + } + + function getProviderPrices( + address provider, + string memory key, + uint256 index + ) public view returns (Lib.ProviderInfo memory) { + Lib.Status storage jobInfo = providers[provider].jobStatus[key][index]; + Lib.ProviderInfo memory providerInfo = providers[provider].info[jobInfo.pricesSetBlockNum]; + return (providerInfo); + } + + /* Returns a list of registered/updated provider's block number */ + function getUpdatedProviderPricesBlocks(address provider) external view returns (uint32[] memory) { + return pricesSetBlockNum[provider]; + } + + function getJobSize(address provider, string memory key) public view returns (uint256) { + require(providers[msg.sender].committedBlock > 0); + return providers[provider].jobStatus[key].length; + } + + /* Returns a list of registered provider Ethereum addresses */ + function getProviders() external view returns (address[] memory) { + return registeredProviders; + } + + /* @dev Check whether or not the given Ethereum address of the provider is + already registered in eBlocBroker. + */ + function doesProviderExist(address provider) external view returns (bool) { + return providers[provider].committedBlock > 0; + } + + /* @dev Check whether or not the enrolled requester's given ORCID iD is + already authenticated in eBlocBroker. */ + function isOrcIDVerified(address user) external view returns (bool) { + if (orcID[user] == "") return false; + return true; + } + + /** + * @dev Check whether or not the given Ethereum address of the requester + * is already registered in eBlocBroker. + * + * @param requester The address of requester + */ + function doesRequesterExist(address requester) public view returns (bool) { + return requesterCommittedBlock[requester] > 0; + } + + function getStorageInfo( + address provider, + address requester, + bytes32 codeHash + ) external view returns (uint256, Lib.JobStorage memory) { + Lib.Provider storage _provider = providers[provider]; + require(_provider.isRunning); + if (requester == address(0)) { + return (0, _provider.jobSt[codeHash]); + } + return (_provider.storageInfo[requester][codeHash].received, _provider.jobSt[codeHash]); + } + + /** + * @dev Returns block numbers where provider's prices are set + * @param provider The address of the provider + */ + function getProviderSetBlockNumbers(address provider) external view returns (uint32[] memory) { + return pricesSetBlockNum[provider]; + } + + /* // used for tests */ + /* // ============== */ + function getProviderReceiptNode(address provider, uint32 index) + external + view + returns ( + uint32, + uint256, + int32 + ) + { + return providers[provider].receiptList.printIndex(index); + } +} + +/* +function requestDataTransferOutDeposit( + string memory key, + uint32 index +) + public + whenProviderRunning +{ + Lib.Provider storage provider = providers[msg.sender]; + Lib.Status storage jobInfo = provider.jobStatus[key][index]; + + require(job.stateCode != Lib.JobStateCodes.COMPLETED && + job.stateCode != Lib.JobStateCodes.REFUNDED && + job.stateCode != Lib.JobStateCodes.COMPLETED_WAITING_ADDITIONAL_DATA_TRANSFER_OUT_DEPOSIT + ); + + job.stateCode = Lib.JobStateCodes.COMPLETED_WAITING_ADDITIONAL_DATA_TRANSFER_OUT_DEPOSIT; + // (msg.sender, key, index) + +} +*/ + +/** + * @dev Log an event for the description of the submitted job. + * + * @param provider The address of the provider. + * @param key The string of the key. + * @param desc The string of the description of the job. + +function setJobDescription( + address provider, + string memory key, + string memory desc +) public returns (bool) { + require(msg.sender == providers[provider].jobStatus[key][0].jobOwner); + emit LogJobDescription(provider, msg.sender, key, desc); + return true; +} +*/ diff --git a/contracts/eBlocBrokerInterface.sol b/contracts/eBlocBrokerInterface.sol new file mode 100644 index 0000000..5a033a9 --- /dev/null +++ b/contracts/eBlocBrokerInterface.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +/* + file: eBlocBrokerInterface.sol + author: Alper Alimoglu + email: alper.alimoglu AT gmail.com +*/ + +pragma solidity >=0.7.0 <0.9.0; + +interface eBlocBrokerInterface { + // Logged when the provider calls the receiveDeposit() method. + // Records the completed jobs' information under receiveDeposit() method call. + event LogProcessPayment( + address indexed provider, + string jobKey, + uint32 index, + uint32 jobID, + uint32 elapsedTime, + address recipient, + uint256 receivedCent, // value in Cent to be recevied by the provider + uint256 refundedCent, // value in Cent to be refunded to the requester + bytes32 resultIpfsHash, + uint256 dataTransferIn, + uint256 dataTransferOut + ); + + /** + * @dev Records the updated jobs' information under setJobState*() method calls + */ + event LogSetJob(address indexed provider, string jobKey, uint32 index, uint32 jobID, uint8 stateCodes); + + // Records the submitted jobs' information under submitJob() method call + event LogJob( + address indexed provider, + address indexed owner, + string jobKey, + uint32 index, + uint8[] cloudStorageID, + bytes32[] sourceCodeHash, + uint8[] cacheType, + uint16[] core, + uint16[] runTime, + uint256 received, // equal to estimated cost + uint256 refunded, + uint256 workflowId + ); + + // Records the registered providers' registered information under + // registerProvider() method call. (fID stands for federationCloudId) + event LogProviderInfo(address indexed provider, bytes32 indexed gpgFingerprint, string gmail, string fID, string ipfsID); + + // Records the registered requesters' registered information under + // registerRequester() method call. + event LogRequester(address indexed requester, bytes32 indexed gpgFingerprint, string gmail, string fID, string ipfsID); + + // Records the refunded jobs' information under refund() method call + event LogRefundRequest(address indexed provider, string jobKey, uint32 index, uint32 jobID, uint256 refundedCent); + + // Logs source code of the registed data files + event LogRegisterData(address indexed provider, bytes32 registeredDataHash); + event LogRegisteredDataRequestToUse(address indexed provider, bytes32 registeredDataHash); + event LogDataStorageRequest(address indexed provider, address owner, bytes32 requestedHash, uint256 paid); + event LogJobDescription(address indexed provider, address requester, string jobKey, string jobDesc); + /** + @notice + * For the requested job, the LogDepositStorage() event logs the storage + deposit transferred to its provider, which was processed either by the + submitJob() or the depositStorage() function. + */ + event LogDepositStorage(address indexed paidAddress, uint256 payment); + + event LogHashROC(address indexed provider, bytes32 hash, uint32 roc, bool isIPFS); + +} diff --git a/tests/conftest.py b/tests/conftest.py index b621975..8ab4e17 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,8 +16,15 @@ def token(Token, accounts): @pytest.fixture(scope="module") -def _Auto(AutonomousSoftwareOrg, accounts): - # _cfg.TOKEN = tx = USDTmy.deploy({"from": accounts[0]}) - # print(tx.address) - # accounts[0].deploy(Lib) - yield AutonomousSoftwareOrg.deploy("0x01234", 2, 3, "0x", {"from": accounts[0]}) +def _Ebb(USDTmy, Lib, eBlocBroker, accounts): + # print("auto") + tx = USDTmy.deploy({"from": accounts[0]}) + accounts[0].deploy(Lib) + yield eBlocBroker.deploy(tx.address, {"from": accounts[0]}) + + +@pytest.fixture(scope="module") +def _Auto(_Ebb, AutonomousSoftwareOrg, accounts): + yield AutonomousSoftwareOrg.deploy( + "0x01234", 2, 3, "0x", _Ebb.address, {"from": accounts[0]} + ) diff --git a/tests/test_transfer.py b/tests/test_transfer.py index 54390bf..b0e1b31 100644 --- a/tests/test_transfer.py +++ b/tests/test_transfer.py @@ -4,14 +4,18 @@ import brownie import pytest import random +from broker.eblocbroker_scripts.utils import Cent auto = None +ebb = None @pytest.fixture(scope="module", autouse=True) -def my_own_session_run_at_beginning(_Auto): +def my_own_session_run_at_beginning(_Auto, _Ebb): global auto # type: ignore + global ebb # type: ignore auto = _Auto + ebb = _Ebb def md5_hash(): @@ -21,21 +25,82 @@ def md5_hash(): def test_AutonomousSoftwareOrg(accounts, token): print(auto.getAutonomousSoftwareOrgInfo()) + _hash = md5_hash() auto.addSoftwareVersionRecord("alper.com", "1.0.0", _hash, {"from": accounts[0]}) + output = auto.getSoftwareVersionRecords(0) log(output[1]) + + se = md5_hash() input_hash = [md5_hash(), "0xabcd"] output_hash = [md5_hash(), "0xabcde"] - auto.addSoftwareExecRecord("1.0.0", "alper.com", input_hash, output_hash) - + auto.addSoftwareExecRecord(se, 0, input_hash, output_hash, {"from": accounts[0]}) input_hash = [md5_hash(), md5_hash(), md5_hash()] output_hash = [md5_hash(), md5_hash(), md5_hash()] - auto.addSoftwareExecRecord("1.0.0", "alper.com", input_hash, output_hash) + auto.addSoftwareExecRecord(se, 0, input_hash, output_hash, {"from": accounts[0]}) + with brownie.reverts(): + output = auto.getSoftwareExecRecord(0) + log(output) + output = auto.getSoftwareExecRecord(1) + log(output) + + GPG_FINGERPRINT = "0359190A05DF2B72729344221D522F92EFA2F330" + provider_gmail = "provider_test@gmail.com" + fid = "ee14ea28-b869-1036-8080-9dbd8c6b1579@b2drop.eudat.eu" + ipfs_address = "/ip4/79.123.177.145/tcp/4001/ipfs/QmWmZQnb8xh3gHf9ZFmVQC4mLEav3Uht5kHJxZtixG3rsf" + price_core_min = Cent("1 cent") + price_data_transfer_mb = Cent("1 cent") + price_storage_hr = Cent("1 cent") + price_cache_mb = Cent("1 cent") + prices = [price_core_min, price_data_transfer_mb, price_storage_hr, price_cache_mb] + available_core = 8 + commitment_bn = 600 + tx = ebb.registerProvider( + GPG_FINGERPRINT, + provider_gmail, + fid, + ipfs_address, + available_core, + prices, + commitment_bn, + {"from": accounts[0]}, + ) + # + input_hash = [md5_hash(), "0xabcd"] + output_hash = [md5_hash(), "0xabcde"] + auto.addSoftwareExecRecord(se, 0, input_hash, output_hash, {"from": accounts[0]}) - output = auto.getSoftwareExecRecord(0) - log(output) - output = auto.getSoftwareExecRecord(1) - log(output) + log() + index = 0 + job_name = f"{se}_{index}" + jobs = [job_name] + for job in jobs: + output = job.split("_") + _se = output[0] + _index = output[1] + output = auto.getIncomings(_se, _index) + for h in output: + _h = str(h)[2:].lstrip("0") + _h = f"0x{_h}" + log(f"{_h} -> {job}", h=False) + + output = auto.getOutgoings(_se, _index) + for h in output: + _h = str(h)[2:].lstrip("0") + _h = f"0x{_h}" + log(f"{job} -> {_h}", h=False) + + log(output) + + # output = auto.getSoftwareExecRecord(0) + # log(output) breakpoint() # DEBUG + + +""" +digraph G { + +} +""" From 74870b6e3696b3b066578493a2858c0d8282047f Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Wed, 29 Nov 2023 15:28:44 +0300 Subject: [PATCH 05/10] Add vis-network example --- vis-network/graph.html | 205 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 vis-network/graph.html diff --git a/vis-network/graph.html b/vis-network/graph.html new file mode 100644 index 0000000..a29973b --- /dev/null +++ b/vis-network/graph.html @@ -0,0 +1,205 @@ + + + + Vis Network | Events | Interaction events + + + + + + +

+ Create a simple network with some nodes and edges. Some of the events are + logged in the console in improve readability. +

+ +
+ +

+

+
+        
+    
+

From d202a5f63156fbb234f94988203a3768a4b0c0a7 Mon Sep 17 00:00:00 2001
From: avatar-lavventura 
Date: Thu, 30 Nov 2023 11:53:32 +0000
Subject: [PATCH 06/10] Generate vis-network example

---
 tests/test_transfer.py | 71 +++++++++++++++++++++++++++++++++++++++---
 vis-network/README.org |  3 ++
 2 files changed, 69 insertions(+), 5 deletions(-)
 create mode 100644 vis-network/README.org

diff --git a/tests/test_transfer.py b/tests/test_transfer.py
index b0e1b31..c1ee44b 100644
--- a/tests/test_transfer.py
+++ b/tests/test_transfer.py
@@ -69,33 +69,94 @@ def test_AutonomousSoftwareOrg(accounts, token):
     #
     input_hash = [md5_hash(), "0xabcd"]
     output_hash = [md5_hash(), "0xabcde"]
-    auto.addSoftwareExecRecord(se, 0, input_hash, output_hash, {"from": accounts[0]})
-
-    log()
     index = 0
+    auto.addSoftwareExecRecord(
+        se, index, input_hash, output_hash, {"from": accounts[0]}
+    )
+    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+    log()
     job_name = f"{se}_{index}"
     jobs = [job_name]
+
+    counter = 1
+    nodes = {}
     for job in jobs:
         output = job.split("_")
         _se = output[0]
         _index = output[1]
+        nodes[counter] = job
+        counter += 1
         output = auto.getIncomings(_se, _index)
         for h in output:
             _h = str(h)[2:].lstrip("0")
-            _h = f"0x{_h}"
+            # _h = f"0x{_h}"
             log(f"{_h} -> {job}", h=False)
+            nodes[counter] = _h
+            counter += 1
 
         output = auto.getOutgoings(_se, _index)
         for h in output:
             _h = str(h)[2:].lstrip("0")
-            _h = f"0x{_h}"
+            # _h = f"0x{_h}"
             log(f"{job} -> {_h}", h=False)
+            nodes[counter] = _h
+            counter += 1
 
         log(output)
 
     # output = auto.getSoftwareExecRecord(0)
     # log(output)
 
+    log("var nodes = new vis.DataSet([")
+    for key, value in nodes.items():
+        log("    {")
+        log(f"       id: {key},")
+        log(f'       label: "{value}",')
+        log(f'       title: "I have popup"')
+        if "_" in value:
+            log('       color: "#7BE141",')
+
+        log("    },")
+
+    log("]);")
+    log("var edges = new vis.DataSet([")
+    for job in jobs:
+        output = job.split("_")
+        _se = output[0]
+        _index = output[1]
+        nodes[counter] = _se
+        counter += 1
+        output = auto.getIncomings(_se, _index)
+        for h in output:
+            _h = str(h)[2:].lstrip("0")
+            # _h = f"0x{_h}"
+            # log(f"{_h} -> {job}", h=False)
+            _from = [*nodes.keys()][[*nodes.values()].index(_h)]
+            _to = [*nodes.keys()][[*nodes.values()].index(job)]
+
+            log("    { ", end="")
+            log(f'from: {_from}, to: {_to}, arrows: "to", color: ', end="")
+            log('{ color: "red" } },')
+            nodes[counter] = _h
+            counter += 1
+
+        output = auto.getOutgoings(_se, _index)
+        for h in output:
+            _h = str(h)[2:].lstrip("0")
+            # _h = f"0x{_h}"
+            # log(f"{job} -> {_h}", h=False)
+            _from = [*nodes.keys()][[*nodes.values()].index(job)]
+            _to = [*nodes.keys()][[*nodes.values()].index(_h)]
+            log("    { ", end="")
+            log(f'from: {_from}, to: {_to}, arrows: "to", color: ', end="")
+            log('{ color: "blue" } },')
+            nodes[counter] = _h
+            counter += 1
+
+        log("]);")
+
+        # log(output)
+
     breakpoint()  # DEBUG
 
 
diff --git a/vis-network/README.org b/vis-network/README.org
new file mode 100644
index 0000000..77acd37
--- /dev/null
+++ b/vis-network/README.org
@@ -0,0 +1,3 @@
+* README
+
+- [[https://visjs.github.io/vis-network/examples/network/events/interactionEvents.html]]

From 1baa3a1d52c81475f8cc7161effa7a07da1ce4c6 Mon Sep 17 00:00:00 2001
From: avatar-lavventura 
Date: Fri, 1 Dec 2023 13:42:34 +0000
Subject: [PATCH 07/10] Convert first test in 2017 to brownie

---
 contracts/AutonomousSoftwareOrg.sol | 21 ++++----
 tests/_test.py                      |  9 ++--
 tests/test_transfer.py              | 80 ++++++++++++++++++++---------
 vis-network/graph.html              | 60 +++++++++++++++++-----
 4 files changed, 120 insertions(+), 50 deletions(-)

diff --git a/contracts/AutonomousSoftwareOrg.sol b/contracts/AutonomousSoftwareOrg.sol
index 8955482..3be7036 100644
--- a/contracts/AutonomousSoftwareOrg.sol
+++ b/contracts/AutonomousSoftwareOrg.sol
@@ -262,16 +262,19 @@ contract AutonomousSoftwareOrg {
 
     function addSoftwareExecRecord(bytes32 sourceCodeHash, uint32 index, bytes32[] memory inputHash, bytes32[] memory outputHash)
         public member(msg.sender) {
-        if (eBlocBroker(eBlocBrokerAddress).doesProviderExist(msg.sender)) {
-            for (uint256 i = 0; i < inputHash.length; i++) {
-                incoming[sourceCodeHash][index].push(inputHash[i]);
-            }
-            for (uint256 i = 0; i < outputHash.length; i++) {
-                outgoing[sourceCodeHash][index].push(outputHash[i]);
-            }
-            // execRecords.push(SoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash));
-            emit LogSoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash);
+        require(eBlocBroker(eBlocBrokerAddress).doesProviderExist(msg.sender));
+        for (uint256 i = 0; i < inputHash.length; i++) {
+            incoming[sourceCodeHash][index].push(inputHash[i]);
+        }
+        for (uint256 i = 0; i < outputHash.length; i++) {
+            outgoing[sourceCodeHash][index].push(outputHash[i]);
         }
+        // execRecords.push(SoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash));
+        emit LogSoftwareExecRecord(msg.sender, sourceCodeHash, index, inputHash, outputHash);
+    }
+
+    function delSoftwareExecRecord(bytes32 sourceCodeHash, uint32 index) {
+        delete incoming[sourceCodeHash][index];
     }
 
     function getIncomings(bytes32 sourceCodeHash, uint32 index) public view returns(bytes32[] memory){
diff --git a/tests/_test.py b/tests/_test.py
index 6c5e182..90542d2 100644
--- a/tests/_test.py
+++ b/tests/_test.py
@@ -51,17 +51,17 @@ def _test_AutonomousSoftwareOrg(web3, accounts, chain):
     print(url + "|" + memberaddr)
 
     web3.eth.defaultAccount = accounts[0]
-    # 0=>1
+    # 0 => 1
     tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(2)
     contract_address = chain.wait.for_receipt(tx)
 
     web3.eth.defaultAccount = accounts[1]
-    # 1=>2
+    # 1 => 2
     tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(3)
     contract_address = chain.wait.for_receipt(tx)
 
     web3.eth.defaultAccount = accounts[0]
-    # 0=>2
+    # 0 => 2
     tx = autonomousSoftwareOrg.transact().VoteMemberCandidate(3)
     contract_address = chain.wait.for_receipt(tx)
 
@@ -69,7 +69,6 @@ def _test_AutonomousSoftwareOrg(web3, accounts, chain):
     web3.eth.defaultAccount = accounts[0]
     url, memberaddr, votecount = autonomousSoftwareOrg.call().getMemberInfo(2)
     print(url + "|" + memberaddr + "|" + str(votecount))
-
     (
         softwarename,
         balance,
@@ -201,5 +200,5 @@ def _test_AutonomousSoftwareOrg(web3, accounts, chain):
     )
 
     # web3.eth.defaultAccount = accounts[0];
-    # set_txn_hash     = autonomousSoftwareOrg.transact().WithdrawProposalFund(0); #fails not enough vote.
+    # set_txn_hash     = autonomousSoftwareOrg.transact().WithdrawProposalFund(0); # fails not enough vote
     # contract_address = chain.wait.for_receipt(tx)
diff --git a/tests/test_transfer.py b/tests/test_transfer.py
index c1ee44b..8e37722 100644
--- a/tests/test_transfer.py
+++ b/tests/test_transfer.py
@@ -23,12 +23,42 @@ def md5_hash():
     return "%032x" % _hash
 
 
-def test_AutonomousSoftwareOrg(accounts, token):
+def test_paper(web3, accounts, token):
     print(auto.getAutonomousSoftwareOrgInfo())
+    auto.BecomeMemberCandidate("0x", {"from": accounts[1]})
+    auto.BecomeMemberCandidate("0x", {"from": accounts[2]})
+    auto.BecomeMemberCandidate("0x", {"from": accounts[3]})
+    assert auto.getMemberInfoLength() == 4
+    log(auto.getCandidateMemberInfo(3))
+    auto.VoteMemberCandidate(2, {"from": accounts[0]})
+    auto.VoteMemberCandidate(3, {"from": accounts[1]})
+    auto.VoteMemberCandidate(3, {"from": accounts[0]})
+    log(auto.getMemberInfo(2, {"from": accounts[0]}))
+    log(auto.getAutonomousSoftwareOrgInfo())
+    log(auto.getMemberInfo(2, {"from": accounts[2]}))
+    auto.DelVoteMemberCandidate(3, {"from": accounts[0]})
+    auto.VoteMemberCandidate(3, {"from": accounts[0]})
+    log(auto.getMemberInfo(2, {"from": accounts[2]}))
+    log(auto.getAutonomousSoftwareOrgInfo())
+    auto.Donate({"from": accounts[5], "value": web3.toWei(2, "wei")})
+    auto.Donate({"from": accounts[6], "value": web3.toWei(2, "wei")})
+    blockNum = web3.eth.blockNumber
+    auto.ProposeProposal(
+        "Prop0", "1.0.0", "0x", 4, blockNum + 30, {"from": accounts[2]}
+    )
+    log(auto.getProposal(0))
+    auto.VoteForProposal(0, {"from": accounts[0]})
+    auto.VoteForProposal(0, {"from": accounts[1]})
+    auto.WithdrawProposalFund(0, {"from": accounts[2]})
+    log(auto.getProposal(0))
+    with brownie.reverts():
+        #: fails there is not enough vote
+        auto.WithdrawProposalFund(0, {"from": accounts[0]})
 
+
+def test_AutonomousSoftwareOrg(accounts, token):
     _hash = md5_hash()
     auto.addSoftwareVersionRecord("alper.com", "1.0.0", _hash, {"from": accounts[0]})
-
     output = auto.getSoftwareVersionRecords(0)
     log(output[1])
 
@@ -68,16 +98,21 @@ def test_AutonomousSoftwareOrg(accounts, token):
     )
     #
     input_hash = [md5_hash(), "0xabcd"]
-    output_hash = [md5_hash(), "0xabcde"]
+    output_hash = [md5_hash(), "0xabcde", md5_hash()]
     index = 0
     auto.addSoftwareExecRecord(
         se, index, input_hash, output_hash, {"from": accounts[0]}
     )
+    #
+    input_hash_1 = [output_hash[0], output_hash[1]]
+    output_hash_1 = [md5_hash()]
+    se_2 = md5_hash()
+    auto.addSoftwareExecRecord(
+        se_2, index, input_hash_1, output_hash_1, {"from": accounts[0]}
+    )
     # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
     log()
-    job_name = f"{se}_{index}"
-    jobs = [job_name]
-
+    jobs = [f"{se}_{index}", f"{se_2}_{index}"]
     counter = 1
     nodes = {}
     for job in jobs:
@@ -90,35 +125,41 @@ def test_AutonomousSoftwareOrg(accounts, token):
         for h in output:
             _h = str(h)[2:].lstrip("0")
             # _h = f"0x{_h}"
-            log(f"{_h} -> {job}", h=False)
-            nodes[counter] = _h
-            counter += 1
+            try:
+                [*nodes.keys()][[*nodes.values()].index(_h)]
+            except:  # noqa
+                log(f"{_h} -> {job}", h=False)
+                nodes[counter] = _h
+                counter += 1
 
         output = auto.getOutgoings(_se, _index)
         for h in output:
             _h = str(h)[2:].lstrip("0")
             # _h = f"0x{_h}"
-            log(f"{job} -> {_h}", h=False)
-            nodes[counter] = _h
-            counter += 1
+            try:
+                [*nodes.keys()][[*nodes.values()].index(_h)]
+            except:  # noqa
+                log(f"{job} -> {_h}", h=False)
+                nodes[counter] = _h
+                counter += 1
 
         log(output)
 
     # output = auto.getSoftwareExecRecord(0)
     # log(output)
-
     log("var nodes = new vis.DataSet([")
     for key, value in nodes.items():
         log("    {")
         log(f"       id: {key},")
         log(f'       label: "{value}",')
-        log(f'       title: "I have popup"')
+        log('       title: "I have popup",')
         if "_" in value:
             log('       color: "#7BE141",')
 
         log("    },")
 
     log("]);")
+    # -----
     log("var edges = new vis.DataSet([")
     for job in jobs:
         output = job.split("_")
@@ -153,15 +194,8 @@ def test_AutonomousSoftwareOrg(accounts, token):
             nodes[counter] = _h
             counter += 1
 
-        log("]);")
+    log("]);")
 
-        # log(output)
+    # log(output)
 
     breakpoint()  # DEBUG
-
-
-"""
-digraph G {
-
-}
-"""
diff --git a/vis-network/graph.html b/vis-network/graph.html
index a29973b..da1c3e6 100644
--- a/vis-network/graph.html
+++ b/vis-network/graph.html
@@ -28,25 +28,59 @@