pragma solidity >=0.5.0 <0.6.0; import "./UserStore.sol"; import "./License.sol"; import "./ArbitrationLicense.sol"; import "../common/SecuredFunctions.sol"; import "../common/USDStakable.sol"; import "../common/Medianizer.sol"; import "../proxy/Proxiable.sol"; /** * @title OfferStore * @dev Offers registry */ contract OfferStore is USDStakable, SecuredFunctions, Proxiable { struct Offer { int16 margin; uint[] paymentMethods; uint limitL; uint limitU; address asset; string currency; address payable owner; address payable arbitrator; bool deleted; } License public sellingLicenses; ArbitrationLicense public arbitrationLicenses; UserStore public userStore; Offer[] public offers; mapping(address => uint256[]) public addressToOffers; mapping(address => mapping (uint256 => bool)) public offerWhitelist; uint public maxOffers = 10; mapping(address => uint) public offerCnt; event OfferAdded( address owner, uint256 offerId, address asset, string location, string currency, string username, uint[] paymentMethods, uint limitL, uint limitU, int16 margin ); event OfferRemoved(address owner, uint256 offerId); /** * @param _userStore User store contract address * @param _sellingLicenses Sellers licenses contract address * @param _arbitrationLicenses Arbitrators licenses contract address * @param _burnAddress Address to send slashed offer funds * @param _medianizer DAI medianizer to obtain USD price */ constructor(address _userStore, address _sellingLicenses, address _arbitrationLicenses, address payable _burnAddress, address _medianizer) public USDStakable(_burnAddress, _medianizer) { init(_userStore, _sellingLicenses, _arbitrationLicenses, _burnAddress, _medianizer); } /** * @dev Initialize contract (used with proxy). Can only be called once * @param _userStore User store contract address * @param _sellingLicenses Sellers licenses contract address * @param _arbitrationLicenses Arbitrators licenses contract address * @param _burnAddress Address to send slashed offer funds */ function init( address _userStore, address _sellingLicenses, address _arbitrationLicenses, address payable _burnAddress, address _medianizer ) public { assert(_initialized == false); _initialized = true; userStore = UserStore(_userStore); sellingLicenses = License(_sellingLicenses); arbitrationLicenses = ArbitrationLicense(_arbitrationLicenses); burnAddress = _burnAddress; maxOffers = 10; basePrice = 1 ether; // 1 USD medianizer = Medianizer(_medianizer); _setOwner(msg.sender); } function updateCode(address newCode) public onlyOwner { updateCodeAddress(newCode); } event LicensesChanged(address sender, address oldSellingLicenses, address newSellingLicenses, address oldArbitrationLicenses, address newArbitrationLicenses); /** * @dev Change license addresses * @param _sellingLicenses Sellers licenses contract address * @param _arbitrationLicenses Arbitrators licenses contract address */ function setLicenses( address _sellingLicenses, address _arbitrationLicenses ) public onlyOwner { emit LicensesChanged(msg.sender, address(sellingLicenses), address(_sellingLicenses), address(arbitrationLicenses), (_arbitrationLicenses)); sellingLicenses = License(_sellingLicenses); arbitrationLicenses = ArbitrationLicense(_arbitrationLicenses); } event MaxOffersChanged(address sender, uint oldMax, uint newMax); /** * @dev Change max offers allowed per seller * @param _newMax New max offers amount */ function setMaxOffers( uint _newMax ) public onlyOwner { emit MaxOffersChanged(msg.sender, maxOffers, _newMax); maxOffers = _newMax; } /** * @dev Add a new offer with a new user if needed to the list * @param _asset The address of the erc20 to exchange, pass 0x0 for Eth * @param _contactData Contact Data ContactType:UserId * @param _location The location on earth * @param _currency The currency the user want to receive (USD, EUR...) * @param _username The username of the user * @param _paymentMethods The list of the payment methods the user accept * @param _limitL Lower limit accepted * @param _limitU Upper limit accepted * @param _margin The margin for the user * @param _arbitrator The arbitrator used by the offer */ function addOffer( address _asset, string memory _contactData, string memory _location, string memory _currency, string memory _username, uint[] memory _paymentMethods, uint _limitL, uint _limitU, int16 _margin, address payable _arbitrator ) public payable { //require(sellingLicenses.isLicenseOwner(msg.sender), "Not a license owner"); require(offerCnt[msg.sender] < maxOffers, "Exceeds the max number of offers"); require(arbitrationLicenses.isAllowed(msg.sender, _arbitrator), "Arbitrator does not allow this transaction"); require(_limitL <= _limitU, "Invalid limits"); require(msg.sender != _arbitrator, "Cannot arbitrate own offers"); userStore.addOrUpdateUser( msg.sender, _contactData, _location, _username ); Offer memory newOffer = Offer( _margin, _paymentMethods, _limitL, _limitU, _asset, _currency, msg.sender, _arbitrator, false ); uint256 offerId = offers.push(newOffer) - 1; offerWhitelist[msg.sender][offerId] = true; addressToOffers[msg.sender].push(offerId); offerCnt[msg.sender]++; emit OfferAdded( msg.sender, offerId, _asset, _location, _currency, _username, _paymentMethods, _limitL, _limitU, _margin); _stake(offerId, msg.sender, _asset); } /** * @notice Remove user offer * @dev Removed offers are marked as deleted instead of being deleted * @param _offerId Id of the offer to remove */ function removeOffer(uint256 _offerId) external { require(offerWhitelist[msg.sender][_offerId], "Offer does not exist"); offers[_offerId].deleted = true; offerWhitelist[msg.sender][_offerId] = false; emit OfferRemoved(msg.sender, _offerId); if(offerCnt[msg.sender] - 1 > offerCnt[msg.sender]){ offerCnt[msg.sender] = 0; } else { offerCnt[msg.sender]--; } _unstake(_offerId); } /** * @notice Get the offer by Id * @dev normally we'd access the offers array, but it would not return the payment methods * @param _id Offer id * @return Offer data (see Offer struct) */ function offer(uint256 _id) external view returns ( address asset, string memory currency, int16 margin, uint[] memory paymentMethods, uint limitL, uint limitU, address payable owner, address payable arbitrator, bool deleted ) { Offer memory theOffer = offers[_id]; // In case arbitrator rejects the seller address payable offerArbitrator = theOffer.arbitrator; if(!arbitrationLicenses.isAllowed(theOffer.owner, offerArbitrator)){ offerArbitrator = address(0); } return ( theOffer.asset, theOffer.currency, theOffer.margin, theOffer.paymentMethods, theOffer.limitL, theOffer.limitU, theOffer.owner, offerArbitrator, theOffer.deleted ); } /** * @notice Get the offer's owner by Id * @dev Helper function * @param _id Offer id * @return Seller address */ function getOfferOwner(uint256 _id) external view returns (address payable) { return (offers[_id].owner); } /** * @notice Get the offer's asset by Id * @dev Helper function * @param _id Offer id * @return Token address used in the offer */ function getAsset(uint256 _id) external view returns (address) { return (offers[_id].asset); } /** * @notice Get the offer's arbitrator by Id * @dev Helper function * @param _id Offer id * @return Arbitrator address */ function getArbitrator(uint256 _id) external view returns (address payable) { return (offers[_id].arbitrator); } /** * @notice Get the size of the offers * @return Number of offers stored in the contract */ function offersSize() external view returns (uint256) { return offers.length; } /** * @notice Get all the offer ids of the address in params * @param _address Address of the offers * @return Array of offer ids for a specific address */ function getOfferIds(address _address) external view returns (uint256[] memory) { return addressToOffers[_address]; } /** * @dev Slash offer stake. If the sender is not the escrow contract, nothing will happen * @param _offerId Offer Id to slash */ function slashStake(uint _offerId) external onlyAllowedContracts { _slash(_offerId); } /** * @dev Refunds a stake. Can be called automatically after an escrow is released * @param _offerId Offer Id to slash */ function refundStake(uint _offerId) external onlyAllowedContracts { _refundStake(_offerId); } }