327 lines
9.8 KiB
Solidity

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);
}
}