diff --git a/contract/marketplace.abi.json b/contract/marketplace.abi.json deleted file mode 100644 index ef509c3e..00000000 --- a/contract/marketplace.abi.json +++ /dev/null @@ -1,110 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "buyProduct", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getProductsLength", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "readProduct", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - }, - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_image", - "type": "string" - }, - { - "internalType": "string", - "name": "_description", - "type": "string" - }, - { - "internalType": "string", - "name": "_location", - "type": "string" - }, - { - "internalType": "uint256", - "name": "_price", - "type": "uint256" - } - ], - "name": "writeProduct", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/contract/propertyMarketplace.sol b/contract/propertyMarketplace.sol index 296b4cdd..b5bdeeb0 100644 --- a/contract/propertyMarketplace.sol +++ b/contract/propertyMarketplace.sol @@ -5,28 +5,45 @@ pragma solidity >=0.7.0 <0.9.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract HouseToken is ERC20 { - constructor(string memory tokenName, string memory symbol, uint supply, address owner) ERC20(tokenName, symbol) { - _mint(owner, supply * 10 ** decimals()); + constructor( + string memory tokenName, + string memory symbol, + uint supply, + address owner + ) ERC20(tokenName, symbol) { + _mint(owner, supply * 10**decimals()); } } - interface IERC20Token { - function transfer(address, uint256) external returns (bool); - function approve(address, uint256) external returns (bool); - function transferFrom(address, address, uint256) external returns (bool); - function totalSupply() external view returns (uint256); - function balanceOf(address) external view returns (uint256); - function allowance(address, address) external view returns (uint256); - - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); + function transfer(address, uint256) external returns (bool); + + function approve(address, uint256) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address) external view returns (uint256); + + function allowance(address, address) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); } contract PropertyMarketplace { - - uint internal propertiesLength = 0; - address internal cUsdTokenAddress = 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1; + uint private propertiesLength = 0; + address internal cUsdTokenAddress = + 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1; struct Property { address payable owner; @@ -38,14 +55,14 @@ contract PropertyMarketplace { HouseToken houseToken; } - struct PropertyLabel{ + struct PropertyLabel { string name; string image; string description; string location; } - struct PropertyStockData{ + struct PropertyStockData { uint price; uint sold; uint numShares; @@ -57,23 +74,57 @@ contract PropertyMarketplace { SoldOut } - mapping (uint => Property) internal properties; - - + mapping(uint => Property) internal properties; + mapping(uint => bool) private exists; + /// @dev checks if a property exist + modifier exist(uint _index) { + require(exists[_index], "Query of non existent property"); + _; + } + /// @dev checks if caller is the property owner + modifier onlyPropertyOwner(uint _index) { + require(properties[_index].owner == msg.sender, "Unauthorised caller"); + _; + } + /// @dev checks if any property's shares have been sold + modifier onlySharesNotSold(uint _index) { + require( + properties[_index].stockData.sold == 0, + "The sale cannot be updated if some tokens are already issued" + ); + _; + } + /// @dev checks if sale of property has been cancelled + modifier isNotCancelled(uint _index) { + require( + properties[_index].status != Status.SaleCancelled, + "The property sale is cancelled" + ); + _; + } + /// @dev creates a property function writeProperty( - PropertyLabel memory _label, + PropertyLabel calldata _label, PropertyStockData memory _stockData, uint _bedrooms, uint _bathrooms ) public { - uint _sold = 0; - _stockData.sold = _sold; - + _stockData.sold = 0; + require( + _stockData.numShares > 0, + "Number of shares needs to be at least one" + ); + require(_stockData.price > 0, "Invalid share price"); //create a house token - there will be as many tokens as there are shares to be sold - HouseToken _houseToken = new HouseToken(_label.name, "HTK", _stockData.numShares, address(this)); - + HouseToken _houseToken = new HouseToken( + _label.name, + "HTK", + _stockData.numShares, + address(this) + ); + exists[propertiesLength] = true; properties[propertiesLength] = Property( payable(msg.sender), _label, @@ -84,192 +135,170 @@ contract PropertyMarketplace { _houseToken ); propertiesLength++; - - } - function readProperty(uint _index) public view returns ( - address payable, - PropertyLabel memory, - PropertyStockData memory, - uint, - uint, - Status, - HouseToken - ) { - return ( - properties[_index].owner, - properties[_index].label, - properties[_index].stockData, - properties[_index].bedrooms, - properties[_index].bathrooms, - properties[_index].status, - properties[_index].houseToken - ); + /// @dev returns the data of a property + function readProperty(uint _index) + public + view + exist(_index) + returns (Property memory) + { + return (properties[_index]); } - function getPropertiesLength() - public - view - returns (uint){ + function getPropertiesLength() public view returns (uint) { return propertiesLength; } + /// @dev returns the number of tokens spender has been approved for function getAllowance(address spender, uint _index) - public - view - returns (uint){ - return properties[_index].houseToken.allowance(properties[_index].owner, spender); + public + view + exist(_index) + returns (uint) + { + return + properties[_index].houseToken.allowance( + properties[_index].owner, + spender + ); } - function getTokenAddress(uint index) - public - view - returns(address){ - return address(properties[index].houseToken); + /// @dev returns the number of shares of address owner + function getBalance(uint _index, address owner) + public + view + exist(_index) + returns (uint) + { + return properties[_index].houseToken.balanceOf(owner); } - function getBalance(uint index,address owner) - public - view - returns(uint){ - return properties[index].houseToken.balanceOf(owner); - } - function approveSpender(address spender, uint index) public { - properties[index].houseToken.approve(spender, 10); + /// @dev approves address spender to use a specified number of tokens + function approveSpender( + address spender, + uint _index, + uint amount + ) external exist(_index) onlyPropertyOwner(_index) { + require(spender != address(0), "invalid spender"); + require(amount <= properties[_index].houseToken.totalSupply()); + properties[_index].houseToken.approve(spender, amount); } function getPropertyPricePerToken(uint _index) - public - view - returns(uint){ - return properties[_index].stockData.price/properties[_index].stockData.numShares; - - } - - function getPropertyPrice(uint _index) - public - view - returns(uint){ - return properties[_index].stockData.price; - - } - - function getPropertyTotalTokens(uint _index) - public - view - returns(uint){ - return properties[_index].stockData.numShares; - - } - - function getPropertyTokensRemaining(uint _index) - public - view - returns(uint){ - return properties[_index].stockData.numShares - properties[_index].stockData.sold; - - } - - function canBeCancelled(uint _index) - public - view - returns(bool) + public + view + exist(_index) + returns (uint) { - return ( - msg.sender==properties[_index].owner || - properties[_index].stockData.sold==0 || - properties[_index].status!=Status.SaleCancelled - ); + return + properties[_index].stockData.price / + properties[_index].stockData.numShares; } - function canUpdatePrice(uint _index) - public - view - returns(bool) + /// @dev returns the number of shares available for sale + function getPropertyTokensRemaining(uint _index) + public + view + exist(_index) + returns (uint) { - return ( - msg.sender==properties[_index].owner || - properties[_index].stockData.sold==0 || - properties[_index].status!=Status.SaleCancelled - ); + return + properties[_index].stockData.numShares - + properties[_index].stockData.sold; } - function canUpdateShares(uint _index) - public - view - returns(bool) + /// @dev returns a bool on if changes can be made to a property + function canModifyProperty(uint _index) + public + view + exist(_index) + returns (bool) { - return ( - msg.sender==properties[_index].owner || - properties[_index].stockData.sold==0 || - properties[_index].status!=Status.SaleCancelled - ); + return (msg.sender == properties[_index].owner && + properties[_index].stockData.sold == 0 && + properties[_index].status != Status.SaleCancelled); } - + /// @dev updates the property price function updatePropertyPrice(uint _index, uint _price) - public + public + exist(_index) + onlyPropertyOwner(_index) + onlySharesNotSold(_index) + isNotCancelled(_index) { - require(msg.sender==properties[_index].owner, "Only the property owner can cancel the sale"); - require(properties[_index].stockData.sold==0, "The sale cannot be updated if some tokens are already issued"); - require(properties[_index].status!=Status.SaleCancelled, "The property sale is cancelled"); + require(_price > 0); properties[_index].stockData.price = _price; } + /// @dev cancels the sale of a property function cancelPropertySale(uint _index) - public + public + exist(_index) + onlyPropertyOwner(_index) + onlySharesNotSold(_index) + isNotCancelled(_index) { - require(msg.sender==properties[_index].owner, "Only the property owner can cancel the sale"); - require(properties[_index].stockData.sold==0, "The sale cannot be cancelled if some tokens are already issued"); properties[_index].status = Status.SaleCancelled; } + /// @dev updates teh number of shares allocated function updatePropertyShares(uint _index, uint _shares) - public + external + exist(_index) + onlyPropertyOwner(_index) + onlySharesNotSold(_index) + isNotCancelled(_index) { - require(msg.sender==properties[_index].owner, "Only the property owner can cancel the sale"); - require(properties[_index].stockData.sold==0, "The sale cannot be updated if some tokens are already issued"); - require(properties[_index].status!=Status.SaleCancelled, "The property sale is cancelled"); + require(_shares > 0, "Invalid number of shares"); properties[_index].stockData.numShares = _shares; } - - function buyProperty(uint _index) public payable { - require( - properties[_index].status!=Status.SaleCancelled - , - "This property sale is cancelled." - ); + /// @dev buys a share from a property + function buyPropertyShare(uint _index) + external + payable + exist(_index) + isNotCancelled(_index) + { + require( + properties[_index].owner != msg.sender, + "You can't buy shares off your own property" + ); //check if the property is already sold out require( - properties[_index].stockData.sold