/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity >=0.8;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see `ERC20Detailed`.
*/
interface IERC20 {
// Optional functions
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
/**
* @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 `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a `Transfer` event.
*/
function transfer(address recipient, 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.
*
* > 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);
/**
* @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);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8;
interface IERC677Receiver {
function onTokenTransfer(address from, uint256 amount, bytes calldata data) external returns (bool);
}
// SPDX-License-Identifier: MIT
// Copied from https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol
pragma solidity >=0.8;
interface ITokenReceiver {
function onTokenTransfer(address token, address from, uint256 amount, bytes calldata data) external;
}
/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2020 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
* are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity >=0.8;
import "./Ownable.sol";
import "./IERC20.sol";
import "./ITokenReceiver.sol";
import "./IERC677Receiver.sol";
contract Market is Ownable {
address public paymenthub;
address public immutable base; // ERC-20 currency
address public immutable token; // ERC-20 share token
address public constant copyright = 0x29Fe8914e76da5cE2d90De98a64d0055f199d06D; // Aktionariat AG
uint256 private price; // current offer price, without drift
uint256 public increment; // increment
uint256 public driftStart;
uint256 public timeToDrift; // seconds until drift pushes price by one drift increment
int256 public driftIncrement;
uint8 private constant licenseFeeBps = 90;
uint8 private constant BUYING_ENABLED = 0x1;
uint8 private constant SELLING_ENABLED = 0x2;
// more bits to be used by payment hub
uint256 public settings = BUYING_ENABLED | SELLING_ENABLED;
event Trade(address indexed token, address who, bytes ref, int amount, address base, uint totPrice, uint fee, uint newprice);
constructor(address shareToken, uint256 price_, uint256 increment_, address baseCurrency, address owner) {
base = baseCurrency;
token = shareToken;
price = price_;
increment = increment_;
paymenthub = address(0x4d99F8e88BAB0BEe8cD840b1Ad3c0bE4f49c293A);
transferOwnership(owner);
}
function setPrice(uint256 newPrice, uint256 newIncrement) public onlyOwner {
anchorPrice(newPrice);
increment = newIncrement;
}
function hasDrift() public view returns (bool) {
return timeToDrift != 0;
}
// secondsPerStep should be negative for downwards drift
function setDrift(uint256 secondsPerStep, int256 newDriftIncrement) public onlyOwner {
anchorPrice(getPrice());
timeToDrift = secondsPerStep;
driftIncrement = newDriftIncrement;
}
function anchorPrice(uint256 currentPrice) private {
price = currentPrice;
driftStart = block.timestamp;
}
function getPrice() public view returns (uint256) {
return getPriceAtTime(block.timestamp);
}
function getPriceAtTime(uint256 timestamp) public view returns (uint256) {
if (hasDrift()){
uint256 passed = timestamp - driftStart;
int256 drifted = int256(passed / timeToDrift) * driftIncrement;
int256 driftedPrice = int256(price) + drifted;
if (driftedPrice < 0){
return 0;
} else {
return uint256(driftedPrice);
}
} else {
return price;
}
}
function buy(address from, uint256 paid, bytes calldata ref) internal returns (uint256) {
uint shares = getShares(paid);
uint costs = notifyTraded(from, shares, ref);
if (costs < paid){
IERC20(base).transfer(from, paid - costs);
}
IERC20(token).transfer(from, shares);
return shares;
}
function notifyTraded(address from, uint256 shares, bytes calldata ref) internal returns (uint256) {
require(hasSetting(BUYING_ENABLED));
uint costs = getBuyPrice(shares);
price = price + (shares * increment);
emit Trade(token, from, ref, int256(shares), base, costs, 0, getPrice());
return costs;
}
function notifyTrade(address buyer, uint256 shares, bytes calldata ref) public onlyOwner {
notifyTraded(buyer, shares, ref);
}
function notifyTradeAndTransfer(address buyer, uint256 shares, bytes calldata ref) public onlyOwner {
notifyTraded(buyer, shares, ref);
IERC20(token).transfer(buyer, shares);
}
function notifyTrades(address[] calldata buyers, uint256[] calldata shares, bytes[] calldata ref) public onlyOwner {
for (uint i = 0; i < buyers.length; i++) {
notifyTraded(buyers[i], shares[i], ref[i]);
}
}
function notifyTradesAndTransfer(address[] calldata buyers, uint256[] calldata shares, bytes[] calldata ref) public onlyOwner {
for (uint i = 0; i < buyers.length; i++) {
notifyTradeAndTransfer(buyers[i], shares[i], ref[i]);
}
}
/**
* Payment hub might actually have sent another accepted token, including Ether.
*/
function processIncoming(address token_, address from, uint256 amount, bytes calldata ref) public payable returns (uint256) {
require(msg.sender == token_ || msg.sender == base || msg.sender == paymenthub);
if (token_ == token){
return sell(from, amount, ref);
} else if (token_ == base){
return buy(from, amount, ref);
} else {
require(false);
return 0;
}
}
// ERC-677 recipient
function onTokenTransfer(address from, uint256 amount, bytes calldata ref) public returns (bool) {
processIncoming(msg.sender, from, amount, ref);
return true;
}
// ITokenReceiver
function onTokenTransfer(address token_, address from, uint256 amount, bytes calldata ref) public {
processIncoming(token_, from, amount, ref);
}
function buyingEnabled() public view returns (bool){
return hasSetting(BUYING_ENABLED);
}
function sellingEnabled() public view returns (bool){
return hasSetting(SELLING_ENABLED);
}
function hasSetting(uint256 setting) private view returns (bool) {
return settings & setting == setting;
}
function sell(address recipient, uint256 amount, bytes calldata ref) internal returns (uint256) {
require(hasSetting(SELLING_ENABLED));
uint256 totPrice = getSellPrice(amount);
IERC20 baseToken = IERC20(base);
uint256 fee = getLicenseFee(totPrice);
if (fee > 0){
baseToken.transfer(copyright, fee);
}
baseToken.transfer(recipient, totPrice - fee);
price -= amount * increment;
emit Trade(token, recipient, ref, -int256(amount), base, totPrice, fee, getPrice());
return totPrice;
}
function getLicenseFee(uint256 totPrice) public pure returns (uint256) {
return totPrice * licenseFeeBps / 10000;
}
function getSellPrice(uint256 shares) public view returns (uint256) {
return getPrice(getPrice() - (shares * increment), shares);
}
function getBuyPrice(uint256 shares) public view returns (uint256) {
return getPrice(getPrice(), shares);
}
function getPrice(uint256 lowest, uint256 shares) internal view returns (uint256){
if (shares == 0) {
return 0;
} else {
uint256 highest = lowest + (shares - 1) * increment;
return ((lowest + highest) / 2) * shares;
}
}
function getShares(uint256 money) public view returns (uint256) {
uint256 currentPrice = getPrice();
uint256 min = 0;
uint256 max = money / currentPrice;
while (min < max){
uint256 middle = (min + max)/2;
uint256 totalPrice = getPrice(currentPrice, middle);
if (money > totalPrice){
min = middle + 1;
} else {
max = middle;
}
}
return min;
}
function withdrawEther(uint256 amount) public ownerOrHub() {
payable(msg.sender).transfer(amount); // return change
}
function approve(address erc20, address who, uint256 amount) public onlyOwner() {
IERC20(erc20).approve(who, amount);
}
function withdraw(address ercAddress, address to, uint256 amount) public ownerOrHub() {
IERC20(ercAddress).transfer(to, amount);
}
function setPaymentHub(address hub) public onlyOwner() {
paymenthub = hub;
}
function setSettings(uint256 settings_) public onlyOwner() {
settings = settings_;
}
function setEnabled(bool newBuyingEnabled, bool newSellingEnabled) public onlyOwner() {
if (newBuyingEnabled != hasSetting(BUYING_ENABLED)){
settings ^= BUYING_ENABLED;
}
if (newSellingEnabled != hasSetting(SELLING_ENABLED)){
settings ^= SELLING_ENABLED;
}
}
modifier ownerOrHub() {
require(owner == msg.sender || paymenthub == msg.sender, "not owner");
_;
}
}
// SPDX-License-Identifier: MIT
//
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
//
// Modifications:
// - Replaced Context._msgSender() with msg.sender
// - Made leaner
pragma solidity >=0.8;
/**
* @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.
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () {
owner = msg.sender;
emit OwnershipTransferred(address(0), owner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner == msg.sender, "not owner");
_;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
{
"compilationTarget": {
"Market.sol": "Market"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"shareToken","type":"address"},{"internalType":"uint256","name":"price_","type":"uint256"},{"internalType":"uint256","name":"increment_","type":"uint256"},{"internalType":"address","name":"baseCurrency","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"bytes","name":"ref","type":"bytes"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"},{"indexed":false,"internalType":"address","name":"base","type":"address"},{"indexed":false,"internalType":"uint256","name":"totPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newprice","type":"uint256"}],"name":"Trade","type":"event"},{"inputs":[{"internalType":"address","name":"erc20","type":"address"},{"internalType":"address","name":"who","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"base","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buyingEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"copyright","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"driftIncrement","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"driftStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"getBuyPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"totPrice","type":"uint256"}],"name":"getLicenseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"getPriceAtTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"getSellPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"money","type":"uint256"}],"name":"getShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hasDrift","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"increment","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"notifyTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"notifyTradeAndTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"buyers","type":"address[]"},{"internalType":"uint256[]","name":"shares","type":"uint256[]"},{"internalType":"bytes[]","name":"ref","type":"bytes[]"}],"name":"notifyTrades","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"buyers","type":"address[]"},{"internalType":"uint256[]","name":"shares","type":"uint256[]"},{"internalType":"bytes[]","name":"ref","type":"bytes[]"}],"name":"notifyTradesAndTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"onTokenTransfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymenthub","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"ref","type":"bytes"}],"name":"processIncoming","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"sellingEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"secondsPerStep","type":"uint256"},{"internalType":"int256","name":"newDriftIncrement","type":"int256"}],"name":"setDrift","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"newBuyingEnabled","type":"bool"},{"internalType":"bool","name":"newSellingEnabled","type":"bool"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"hub","type":"address"}],"name":"setPaymentHub","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newPrice","type":"uint256"},{"internalType":"uint256","name":"newIncrement","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"settings_","type":"uint256"}],"name":"setSettings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"settings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timeToDrift","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"ercAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawEther","outputs":[],"stateMutability":"nonpayable","type":"function"}]