Skip to content

Instantly share code, notes, and snippets.

@jswny
Last active December 4, 2020 06:51
Show Gist options
  • Save jswny/b94450ece4cefa84a8f7745420ad3d15 to your computer and use it in GitHub Desktop.
Save jswny/b94450ece4cefa84a8f7745420ad3d15 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.7.5+commit.eb77ed08.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.5;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* This test is non-exhaustive, and there may be false-negatives: during the
* execution of a contract's constructor, its address will be reported as
* not containing a contract.
*
* > It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*/
function isContract(address account) internal view returns (bool) {
// This method relies in extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
}
import "./icpmm.sol";
contract Cpmm is IConstantProductMarketMaker {
ITokenHolder private tokenHolderA;
ITokenHolder private tokenHolderB;
uint private const;
bool private inProgress = false;
constructor(uint _tradeFee) IConstantProductMarketMaker(_tradeFee) {
}
// Ownership of the two ITokenHolders passed in the constructor MUST be given to this contract before starting.
// Require that the token balance of either holder is not 0.
// Only the owner of this contract should be able to execute this function!
function start(ITokenHolder _a, ITokenHolder _b) override virtual external onlyOwner {
require(!inProgress, "already started");
require(_a.owner() == address(this) && _b.owner() == address(this), "doens't own both token holders");
require(_a.tokenBalance() > 0 && _b.tokenBalance() > 0, "token holders don't both have a balance greater than zero");
tokenHolderA = _a;
tokenHolderB = _b;
const = _a.tokenBalance() * _b.tokenBalance();
inProgress = true;
}
// Transfer ownership of the TokenHolders back to the owner of the market maker.
// Make sure any further API calls (other than start, and until start is called again) fail via "require"
// Only the owner of this contract should be able to execute this function!
function end() override virtual external onlyOwner {
requireInProgress();
address addy = address(this);
tokenHolderA.transferOwnership(addy);
tokenHolderB.transferOwnership(addy);
inProgress = false;
}
// Pass a number 0 or 1 to get the Token type and current reserve quantity of that item
function getCurrentQuantity(uint which) override virtual external returns (IERC223, uint) {
requireInProgress();
require(which == 0 || which == 1, "invalid token type");
ITokenHolder selected;
if (which == 0) {
selected = tokenHolderA;
} else {
selected = tokenHolderB;
}
return (selected.currency(), selected.tokenBalance());
}
// Returns the value that the product of all reserve quantities must equal or exceed
function getConstantProduct() override virtual external view returns (uint) {
requireInProgress();
return const;
}
// Caller offers some quantity of tokens and expects to receive a different quantity of tokens.
// This function calls the passed "transfer" function and expects that that function will transfer the promised receiveQuantity of receiveFromCallerToken
// After the "transfer" function executes correctly, this function sends giveQuantity of giveToCallerTokens to the caller.
// Make SURE that the constant product constraint is respected. Allow trades that exceed the constant, but if this happens do not change the constant.
function trade(IERC223 giveToCallerToken, uint giveQuantity, address giveDestination, IERC223 receiveFromCallerToken, uint receiveQuantity, function (address) external returns (bool) transfer) override virtual payable external {
requireInProgress();
require(msg.value >= tradeFee);
require(giveToCallerToken == tokenHolderA.currency() || giveToCallerToken == tokenHolderB.currency(), "giveToCallerToken is not one of the valid tokens");
require(receiveFromCallerToken == tokenHolderA.currency() || receiveFromCallerToken == tokenHolderB.currency(), "receiveFromCallerToken is not one of the valid tokens");
require(giveToCallerToken != receiveFromCallerToken, "giveToCallerToken and receiveFromCallerToken are the same token");
// require(receiveFromCallerToken.balanceOf(msg.sender) >= receiveQuantity, "insufficient token funds in receiveFromCallerToken");
ITokenHolder giveToCallerTokenHolder;
ITokenHolder receiveFromCallerTokenHolder;
if (giveToCallerToken == tokenHolderA.currency()) {
giveToCallerTokenHolder = tokenHolderA;
receiveFromCallerTokenHolder = tokenHolderB;
} else {
giveToCallerTokenHolder = tokenHolderB;
receiveFromCallerTokenHolder = tokenHolderA;
}
require((giveToCallerTokenHolder.tokenBalance() - giveQuantity) * (receiveFromCallerTokenHolder.tokenBalance() + receiveQuantity) >= const, "constant is not satisfied");
uint receiveFromCallerTokenHolderBalance = receiveFromCallerTokenHolder.tokenBalance();
// Does this need to be address(this) instead?
require(transfer(address(receiveFromCallerTokenHolder)), "transfer did not succeed");
require(receiveFromCallerTokenHolder.tokenBalance() >= (receiveFromCallerTokenHolderBalance + receiveQuantity), "transfer did not transfer enough tokens");
giveToCallerTokenHolder.withdraw(giveDestination, giveQuantity);
}
function requireInProgress() private view {
require(inProgress, "not started");
}
}
// SPDX-License-Identifier: MIT
// Constant Product Market Maker
pragma solidity >=0.7.5;
import "./cpmm.sol";
contract SetupCpmmTest
{
TokenManager public tok1;
TokenManager public tok2;
TokenHolder public h1;
TokenHolder public h2;
constructor() payable
{
// Create 2 arbitrary token (types)
tok1 = new TokenManager(10, 10);
tok2 = new TokenManager(20, 5);
// Create the 2 holders to be used by the CPMM
h1 = new TokenHolder(tok1);
h2 = new TokenHolder(tok2);
// Load token reserves into these holders
uint aAmt = 10000;
uint charge = tok1.price(aAmt) + tok1.fee(aAmt);
tok1.sellToCaller{value:charge}(address(h1),aAmt);
uint bAmt = 20000;
uint charge2 = tok2.price(bAmt) + tok2.fee(bAmt);
tok2.sellToCaller{value:charge2}(address(h2),bAmt);
}
// Give ownership of these holders to another entity (you need to give ownership to your TestCpmm object)
function transfer(address t) public
{
h1.transferOwnership(t);
h2.transferOwnership(t);
}
}
contract TestCpmm
{
TokenManager public tok1;
TokenManager public tok2;
TokenHolder public h1;
TokenHolder public h2;
Cpmm public cpmm;
constructor()
{
}
function Init(address sAddr) public payable
{
SetupCpmmTest s = SetupCpmmTest(sAddr);
tok1 = s.tok1();
tok2 = s.tok2();
h1 = s.h1();
h2 = s.h2();
// Create a CPMM with a trade fee of 10 wei
cpmm = new Cpmm(10);
}
// A callback function to be passed to CPMM.trade that actually does a token transfer
uint tqty;
TokenHolder tth;
function transfer(address destination) external returns(bool)
{
require(tqty > 0);
require(tth.tokenBalance() >= tqty, "qty");
tth.withdraw(destination, tqty);
tqty = 0;
return true;
}
function Test() public
{
h1.transferOwnership(address(cpmm));
h2.transferOwnership(address(cpmm));
cpmm.start(h1,h2);
// Create two holders that will interact with the CPMM
TokenHolder a1 = new TokenHolder(tok1);
TokenHolder a2 = new TokenHolder(tok2);
// Fund a2 with some tokens
uint amt = 100;
uint charge = tok2.price(amt) + tok2.fee(amt);
tok2.sellToCaller{value:charge}(address(a2),amt);
require(tok2.balanceOf(address(a2)) == amt, "bad fund");
// Buy tok1 for tok2
tqty = 11;
tth = a2;
// Based on the Init function, this CPMM's reserves are 10000 and 20000, so its constant is 10000*20000 = 200000000
// Therefore to buy 5 of tok1 we need at least 11 of tok2 (10000-5)*(20000+11) = 200009945
cpmm.trade{value:10}(a1.currency(), 5, address(a1), a2.currency(), tqty, this.transfer);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.5;
import "./IERC223.sol";
import "./safeMath.sol";
import "./Address.sol";
/**
* @title Reference implementation of the ERC223 standard token.
*/
contract ERC223Token is IERC223 {
using SafeMath for uint;
/**
* @dev See `IERC223.totalSupply`.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
mapping(address => uint) balances; // List of user balances.
/**
* @dev Transfer the specified amount of tokens to the specified address.
* Invokes the `tokenFallback` function if the recipient is a contract.
* The token transfer fails if the recipient is a contract
* but does not implement the `tokenFallback` function
* or the fallback function to receive funds.
*
* @param _to Receiver address.
* @param _value Amount of tokens that will be transferred.
* @param _data Transaction metadata.
*/
function transfer(address _to, uint _value, bytes memory _data) override public returns (bool success){
// Standard function transfer similar to ERC20 transfer with no _data .
// Added due to backwards compatibility reasons .
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
if(Address.isContract(_to)) {
IERC223Recipient receiver = IERC223Recipient(_to);
receiver.tokenFallback(msg.sender, _value, _data);
}
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
/**
* @dev Transfer the specified amount of tokens to the specified address.
* This function works the same with the previous one
* but doesn't contain `_data` param.
* Added due to backwards compatibility reasons.
*
* @param _to Receiver address.
* @param _value Amount of tokens that will be transferred.
*/
function transfer(address _to, uint _value) override public returns (bool success){
bytes memory empty = hex"00000000";
require(balances[msg.sender] >= _value, "transfer more than possess");
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
if(Address.isContract(_to)) {
IERC223Recipient receiver = IERC223Recipient(_to);
receiver.tokenFallback(msg.sender, _value, empty);
}
emit Transfer(msg.sender, _to, _value, empty);
return true;
}
/**
* @dev Returns balance of the `_owner`.
*
* @param _owner The address whose balance will be returned.
* @return balance Balance of the `_owner`.
*/
function balanceOf(address _owner) override public view returns (uint balance) {
return balances[_owner];
}
}
// SPDX-License-Identifier: MIT
// Constant Product Market Maker
pragma solidity >=0.7.5;
import "./IERC223.sol";
import "./ERC223.sol";
import "./ownable.sol";
import "./tokenMgr.sol";
abstract contract IConstantProductMarketMaker is Ownable
{
// How much eth (denominated in wei) must be sent to the trade function to make a trade.
uint public tradeFee;
constructor(uint _tradeFee) {
tradeFee = _tradeFee;
}
// Ownership of the two ITokenHolders passed in the constructor MUST be given to this contract before starting.
// Require that the token balance of either holder is not 0.
// Only the owner of this contract should be able to execute this function!
function start(ITokenHolder _a, ITokenHolder _b) virtual external;
// Transfer ownership of the TokenHolders back to the owner of the market maker.
// Make sure any further API calls (other than start, and until start is called again) fail via "require"
// Only the owner of this contract should be able to execute this function!
function end() virtual external;
// Pass a number 0 or 1 to get the Token type and current reserve quantity of that item
function getCurrentQuantity(uint /*which*/) virtual external returns (IERC223, uint);
// Returns the value that the product of all reserve quantities must equal or exceed
function getConstantProduct() virtual external view returns (uint)
{
}
// Caller offers some quantity of tokens and expects to receive a different quantity of tokens.
// This function calls the passed "transfer" function and expects that that function will transfer the promised receiveQuantity of receiveFromCallerToken
// After the "transfer" function executes correctly, this function sends giveQuantity of giveToCallerTokens to the caller.
// Make SURE that the constant product constraint is respected. Allow trades that exceed the constant, but if this happens do not change the constant.
function trade(IERC223 giveToCallerToken, uint giveQuantity, address giveDestination, IERC223 receiveFromCallerToken, uint receiveQuantity, function (address) external returns (bool) transfer) virtual payable external;
}
/* In a new file called cpmm.sol, create an implementation of IConstantProductMarketMaker with a constructor that takes the fee that should be required per trade:
// contract Cpmm is IConstantProductMarketMaker
{
constructor(uint tradefee)
}
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.5;
/**
* @dev Interface of the ERC777Token standard as defined in the EIP.
*
* This contract uses the
* [ERC1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let
* token holders and recipients react to token movements by using setting implementers
* for the associated interfaces in said registry. See `IERC1820Registry` and
* `ERC1820Implementer`.
*
* https://github.com/ethereum/eips/issues/223
*/
abstract contract IERC223 {
/**
* @dev Returns the total supply of the token.
*/
uint public _totalSupply;
/**
* @dev Returns the balance of the `who` address.
*/
function balanceOf(address who) virtual external view returns (uint);
/**
* @dev Transfers `value` tokens from `msg.sender` to `to` address
* and returns `true` on success.
*/
function transfer(address to, uint value) virtual external returns (bool success);
/**
* @dev Transfers `value` tokens from `msg.sender` to `to` address with `data` parameter
* and returns `true` on success.
*/
function transfer(address to, uint value, bytes calldata data) virtual external returns (bool success);
/**
* @dev Event that is fired on successful transfer.
*/
event Transfer(address indexed from, address indexed to, uint value, bytes data);
}
/**
* @title Contract that will work with ERC223 tokens.
*/
abstract contract IERC223Recipient {
/**
* @dev Standard ERC223 function that will handle incoming token transfers.
*
* @param _from Token sender address.
* @param _value Amount of tokens.
* @param _data Transaction metadata.
*/
function tokenFallback(address _from, uint _value, bytes calldata _data) virtual external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.5;
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function.");
_;
}
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner != address(0)) owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.5;
/**
* @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, "SafeMath: 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, "SafeMath: 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, "SafeMath: multiplication overflow");
return c;
}
/**
* @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, "SafeMath: 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;
}
/**
* @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, "SafeMath: modulo by zero");
return a % b;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.5;
import "./IERC223.sol";
import "./ERC223.sol";
import "./ownable.sol";
abstract contract ITokenHolder is IERC223Recipient, Ownable
{
IERC223 public currency;
uint256 public pricePer; // In wei
uint256 public amtForSale;
// Return the current balance of ethereum held by this contract
function ethBalance() view external returns (uint)
{
return address(this).balance;
}
// Return the quantity of tokens held by this contract
function tokenBalance() virtual external view returns(uint);
// indicate that this contract has tokens for sale at some price, so buyFromMe will be successful
function putUpForSale(uint /*amt*/, uint /*price*/) virtual public onlyOwner
{
assert(false);
}
// This function is called by the buyer to pay in ETH and receive tokens. Note that this contract should ONLY sell the amount of tokens at the price specified by putUpForSale!
function sellToCaller(address /*to*/, uint /*qty*/) virtual public payable
{
assert(false);
}
// buy tokens from another holder. This is OPTIONALLY payable. The caller can provide the purchase ETH, or expect that the contract already holds it.
function buy(uint /*amt*/, uint /*maxPricePer*/, TokenHolder /*seller*/) virtual public payable onlyOwner
{
assert(false);
}
// Owner can send tokens
function withdraw(address /*_to*/, uint /*amount*/) virtual public onlyOwner
{
assert(false);
}
// Sell my tokens back to the token manager
function remit(uint /*amt*/, uint /*_pricePer*/, TokenManager /*mgr*/) virtual public onlyOwner payable
{
assert(false);
}
// Validate that this contract can handle tokens of this type
// You need to define this function in your derived classes, but it is already specified in IERC223Recipient
//function tokenFallback(address /*_from*/, uint /*_value*/, bytes calldata /*_data*/) override external
}
contract TokenHolder is ITokenHolder
{
constructor(IERC223 _cur)
{
currency = _cur;
}
/* If this holder is going to sell tokens for ETH, this function is called by the buyer to pay in ETH and receive tokens */
function sellToCaller(address to, uint qty) virtual override public payable
{
require(amtForSale > 0, "Nothing for sale"); // not for sale
require(qty <= amtForSale, "trying to buy too many");
require(msg.value >= pricePer*qty, "did not provide enough ETH"); // they paid me what was expected
amtForSale -= qty;
currency.transfer(to, qty);
}
/* indicate that this contract has tokens for sale at some price, so buyFromMe will be successful */
function putUpForSale(uint amt, uint price) override public onlyOwner
{
pricePer = price;
amtForSale = amt;
}
/* Sell my tokens back to the token manager */
function remit(uint amt, uint _pricePer, TokenManager mgr) override public onlyOwner payable
{
putUpForSale(amt, _pricePer);
mgr.buyFromCaller{value: mgr.fee(amt)}(amt);
}
// buy tokens from another holder. This is OPTIONALLY payable. The caller can provide the purchase ETH, or expect that the contract already holds it.
function buy(uint amt, uint maxPricePer, TokenHolder seller) override public payable onlyOwner
{
uint sellerPrice = uint(seller.pricePer());
require(maxPricePer >= sellerPrice, "TokenHolder.buy: purchase price low");
uint myBal = currency.balanceOf(address(this));
seller.sellToCaller{value:sellerPrice*amt}(address(this),amt); // This will only work if the seller has put the tokens up for sale by calling putUpForSale
// require that sale happened.
require(currency.balanceOf(address(this)) == myBal + amt, "TokenHolder.buy: xfer did not happen");
}
/* Validate that this contract can handle tokens of this type */
function tokenFallback(address /*_from*/, uint /*_value*/, bytes calldata /*_data*/) override view external
{
require (msg.sender == address(currency), "TokenHolder: wrong token type"); // can we handle this token type?
}
/* Owner can send tokens */
function withdraw(address _to, uint amount) override public onlyOwner
{
currency.transfer(_to, amount);
}
function tokenBalance() override external view returns(uint)
{
return currency.balanceOf(address(this));
}
}
contract TokenManager is ERC223Token, TokenHolder
{
using SafeMath for uint;
uint private weiPerToken=0;
uint private feePerTokenPerOp = 0;
// Pass the price per token, and the fee per token to set up the manager's buy/sell activity
constructor(uint _price, uint _fee) TokenHolder(this) payable
{
weiPerToken = _price;
feePerTokenPerOp = _fee;
}
// Returns the price per token to callers
function price(uint amt) public view returns(uint) { return amt*weiPerToken; }
// Returns the fee per token to callers
function fee(uint amt) public view returns(uint) { return amt*feePerTokenPerOp; }
// Caller buys tokens from this contract
function sellToCaller(address to, uint amount) payable override public
{
//require(msg.value >= amount*(weiPerToken + feePerTokenPerOp), "TM fee");
if (balanceOf(address(this)) < amount)
{
mint(amount);
}
ERC223Token mytok = ERC223Token(this);
mytok.transfer(to,amount);
}
// Caller sells tokens to this contract
function buyFromCaller(uint amount) public payable
{
require(msg.value >= amount*feePerTokenPerOp, "buyFromCaller: low fee");
uint256 iHave = balanceOf(address(this)); // What's my starting token balance?
TokenHolder th = TokenHolder(msg.sender);
th.sellToCaller{value: (amount*weiPerToken)}(address(this), amount); // Tell the other contract to actually transfer & pay it.
require(balanceOf(address(this)) >= iHave + amount,"buyFromCaller: no xfer"); // Did the other contract actually transfer the tokens?
}
// Create some new tokens, and give them to this TokenManager
function mint(uint amount) internal // onlyOwner
{
_totalSupply = _totalSupply.add(amount);
balances[address(this)] = balances[address(this)].add(amount);
}
// Destroy some existing tokens, that are owned by this TokenManager
function melt(uint amount) external onlyOwner
{
require(balanceOf(address(this)) >= amount, "Tried to melt more coins than manager owns");
assert(_totalSupply >= amount);
_totalSupply = _totalSupply.sub(amount);
balances[address(this)] = balances[address(this)].sub(amount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment