Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions solidity/contracts/AugurConstantProductMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ pragma solidity 0.8.29;

import { ERC20 } from "./ERC20.sol";
import { IERC20 } from "./IERC20.sol";
import { IShareToken } from "./IShareToken.sol";
import { IAugurConstantProductShareToken } from "./IAugurConstantProductShareToken.sol";
import { IMarket } from "./IMarket.sol";
import { IAugur } from "./IAugur.sol";
import { Constants } from "./Constants.sol";
import { AddressToString } from "./AddressToString.sol";
import { TokenId } from "./TokenId.sol";

contract AugurConstantProduct is ERC20 {
using AddressToString for address;

IShareToken public shareToken;
IAugurConstantProductShareToken shareToken;
address public augurMarketAddress;
IAugur public constant augur = IAugur(Constants.AUGUR_ADDRESS);
uint256 public INVALID;
Expand All @@ -31,13 +32,13 @@ contract AugurConstantProduct is ERC20 {
unlocked = 1;
}

constructor(IMarket market) ERC20(string(abi.encodePacked("ACPM-", address(market).addressToString())), address(market).addressToString()) {
constructor(IMarket market, IAugurConstantProductShareToken acpmShareToken) ERC20(string(abi.encodePacked("ACPM-", address(market).addressToString())), address(market).addressToString()) {
augurMarketAddress = address(market);
shareToken = market.shareToken();
shareToken = acpmShareToken;
require(augur.getMarketType(market) == 0, "AugurCP: ACPM only supports Yes No Markets");
INVALID = shareToken.getTokenId(augurMarketAddress, 0);
NO = shareToken.getTokenId(augurMarketAddress, 1);
YES = shareToken.getTokenId(augurMarketAddress, 2);
INVALID = TokenId.getTokenId(address(this), 0);
NO = TokenId.getTokenId(address(this), 1);
YES = TokenId.getTokenId(address(this), 2);
}

function mint(address mintTo) lock external {
Expand Down
11 changes: 9 additions & 2 deletions solidity/contracts/AugurConstantProductMarketFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ pragma solidity 0.8.29;

import { IMarket } from "./IMarket.sol";
import { AugurConstantProduct } from "./AugurConstantProductMarket.sol";
import { IAugurConstantProductShareToken } from "./IAugurConstantProductShareToken.sol";
import { ContractExists } from './ContractExists.sol';
import { AddressToString } from './AddressToString.sol';

contract AugurConstantProductMarketFactory {
using ContractExists for address;
using AddressToString for address;

IAugurConstantProductShareToken shareToken;
mapping(address => bool) public isValidMarket;
address[] private marketList;

function initialize(IAugurConstantProductShareToken acpmShareToken) external {
require(address(shareToken) == address(0), "AugurCP: already initialized");
shareToken = acpmShareToken;
}

function createACPM(IMarket market) public returns (AugurConstantProduct) {
address acpmAddress = getACPMAddress(market);
require(!acpmAddress.exists(), string(abi.encodePacked("ACPM for market already exists: ", address(acpmAddress).addressToString())));
{
bytes32 _salt = keccak256(abi.encodePacked(market));
bytes memory _deploymentData = abi.encodePacked(type(AugurConstantProduct).creationCode, abi.encode(market));
bytes memory _deploymentData = abi.encodePacked(type(AugurConstantProduct).creationCode, abi.encode(market, shareToken));
assembly {
acpmAddress := create2(0x0, add(0x20, _deploymentData), mload(_deploymentData), _salt)
if iszero(extcodesize(acpmAddress)) {
Expand All @@ -38,7 +45,7 @@ contract AugurConstantProductMarketFactory {
_const,
address(this),
_salt,
keccak256(abi.encodePacked(type(AugurConstantProduct).creationCode, abi.encode(market)))
keccak256(abi.encodePacked(type(AugurConstantProduct).creationCode, abi.encode(market, shareToken)))
)))));
}

Expand Down
38 changes: 22 additions & 16 deletions solidity/contracts/AugurConstantProductMarketRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,41 @@ pragma solidity 0.8.29;

import { IERC1155 } from "./IERC1155.sol";
import { IERC20 } from "./IERC20.sol";
import { IShareToken } from "./IShareToken.sol";
import { IAugurConstantProductShareToken } from "./IAugurConstantProductShareToken.sol";
import { IMarket } from "./IMarket.sol";
import { IAugur } from "./IAugur.sol";
import { IOICash } from "./IOICash.sol";
import { IAugurConstantProduct } from "./IAugurConstantProduct.sol";
import { Constants } from "./Constants.sol";
import { TokenId } from "./TokenId.sol";

contract AugurConstantProductRouter {

IShareToken shareToken = IShareToken(Constants.SHARE_TOKEN);
IAugurConstantProductShareToken shareToken;
IERC20 public dai = IERC20(Constants.DAI_ADDRESS);
IAugur public constant augur = IAugur(Constants.AUGUR_ADDRESS);
uint256 private constant numTicks = 1000;

constructor() {
dai.approve(Constants.SHARE_TOKEN, 2**256-1);
dai.approve(Constants.AUGUR_ADDRESS, 2**256-1);
}

function initialize(IAugurConstantProductShareToken acpmShareToken) external {
require(address(shareToken) == address(0), "AugurCP: already initialized");
shareToken = acpmShareToken;
dai.approve(address(shareToken), 2**256-1);
}

function addLiquidity(IAugurConstantProduct acpm, uint256 setsToBuy) external {
dai.transferFrom(msg.sender, address(this), setsToBuy * numTicks);
shareToken.buyCompleteSets(acpm.augurMarketAddress(), msg.sender, setsToBuy);
shareToken.buyCompleteSets(address(acpm), msg.sender, setsToBuy);

(uint256 poolNoBalance, uint256 poolYesBalance) = acpm.getNoYesBalances();

uint256[] memory tokenIds = new uint256[](2);
uint256[] memory tokenValues = new uint256[](2);
tokenIds[0] = acpm.NO();
tokenIds[1] = acpm.YES();

tokenIds[0] = TokenId.getTokenId(address(acpm), 1);
tokenIds[1] = TokenId.getTokenId(address(acpm), 2);
if (poolYesBalance == poolNoBalance) {
tokenValues[0] = setsToBuy;
tokenValues[1] = setsToBuy;
Expand All @@ -50,19 +56,19 @@ contract AugurConstantProductRouter {
function removeLiquidity(IAugurConstantProduct acpm, uint256 poolTokensToSell) external {
acpm.transferFrom(msg.sender, address(acpm), poolTokensToSell);
(uint256 noSharesReceived, uint256 yesSharesReceived) = acpm.burn(msg.sender);
uint256 invalidShares = shareToken.balanceOf(msg.sender, acpm.INVALID());
uint256 invalidShares = shareToken.balanceOf(msg.sender, TokenId.getTokenId(address(acpm), 0));

uint256 completeSetsToSell = invalidShares;
completeSetsToSell = noSharesReceived < completeSetsToSell ? noSharesReceived : completeSetsToSell;
completeSetsToSell = yesSharesReceived < completeSetsToSell ? yesSharesReceived : completeSetsToSell;
shareToken.sellCompleteSets(acpm.augurMarketAddress(), msg.sender, msg.sender, completeSetsToSell, bytes32(0));
shareToken.sellCompleteSets(address(acpm), msg.sender, msg.sender, completeSetsToSell);
}

function enterPosition(IAugurConstantProduct acpm, uint256 amountInDai, bool buyYes, uint256 minSharesOut, uint256 deadline) external {
require(block.timestamp < deadline, "AugurCP: Deadline");
uint256 setsToBuy = amountInDai / numTicks;
dai.transferFrom(msg.sender, address(this), amountInDai);
shareToken.buyCompleteSets(acpm.augurMarketAddress(), msg.sender, setsToBuy);
shareToken.buyCompleteSets(address(acpm), msg.sender, setsToBuy);

(uint256 poolNoBalance, uint256 poolYesBalance) = acpm.getNoYesBalances();

Expand All @@ -86,7 +92,7 @@ contract AugurConstantProductRouter {

// short circuit if user is closing out their own complete sets
if (userInvalid >= setsToSell && userNo >= setsToSell && userYes >= setsToSell) {
shareToken.sellCompleteSets(acpm.augurMarketAddress(), msg.sender, msg.sender, setsToSell, bytes32(0));
shareToken.sellCompleteSets(address(acpm), msg.sender, msg.sender, setsToSell);
return;
}

Expand All @@ -102,27 +108,27 @@ contract AugurConstantProductRouter {
uint256 yesNeeded = getAmountIn(noNeeded, poolYesBalance, poolNoBalance, fee);
require(yesNeeded <= yesToSwap, "AugurCP: Not enough YES shares to close out for this amount");
require(setsToSell + yesNeeded <= maxSharesIn, "AugurCP: YES shares needed > maxSharesIn");
shareToken.unsafeTransferFrom(msg.sender, address(acpm), acpm.YES(), yesNeeded);
shareToken.unsafeTransferFrom(msg.sender, address(acpm), TokenId.getTokenId(address(acpm), 2), yesNeeded);
acpm.swap(msg.sender, noNeeded, 0);
} else {
uint256 yesNeeded = setsToSell - userYes;
uint256 noToSwap = userNo - setsToSell;
uint256 noNeeded = getAmountIn(yesNeeded, poolNoBalance, poolYesBalance, fee);
require(noNeeded <= noToSwap, "AugurCP: Not enough No shares to close out for this amount");
require(setsToSell + noNeeded <= maxSharesIn, "AugurCP: No shares needed > maxSharesIn");
shareToken.unsafeTransferFrom(msg.sender, address(acpm), acpm.NO(), noNeeded);
shareToken.unsafeTransferFrom(msg.sender, address(acpm), TokenId.getTokenId(address(acpm), 1), noNeeded);
acpm.swap(msg.sender, 0, yesNeeded);
}

shareToken.sellCompleteSets(acpm.augurMarketAddress(), msg.sender, msg.sender, setsToSell, bytes32(0));
shareToken.sellCompleteSets(address(acpm), msg.sender, msg.sender, setsToSell);
}

function swapExactSharesForShares(IAugurConstantProduct acpm, uint256 inputShares, bool inputYes, uint256 minSharesOut, uint256 deadline) external {
require(block.timestamp < deadline, "AugurCP: Deadline");
(uint256 poolNo, uint256 poolYes) = acpm.getNoYesBalances();
uint256 amountOut = getAmountOut(inputShares, inputYes ? poolYes : poolNo, inputYes ? poolNo : poolYes, acpm.fee());
require(amountOut >= minSharesOut, "AugurCP: shares out < minSharesOut");
shareToken.unsafeTransferFrom(msg.sender, address(acpm), inputYes ? acpm.YES() : acpm.NO(), inputShares);
shareToken.unsafeTransferFrom(msg.sender, address(acpm), inputYes ? TokenId.getTokenId(address(acpm), 2) : TokenId.getTokenId(address(acpm), 1), inputShares);
acpm.swap(msg.sender, inputYes ? amountOut : 0, inputYes ? 0 : amountOut);
}

Expand All @@ -131,7 +137,7 @@ contract AugurConstantProductRouter {
(uint256 poolNo, uint256 poolYes) = acpm.getNoYesBalances();
uint256 amountIn = getAmountIn(outputShares, inputYes ? poolYes : poolNo, inputYes ? poolNo : poolYes, acpm.fee());
require(amountIn <= maxSharesIn, "AugurCP: shares in > maxSharesIn");
shareToken.unsafeTransferFrom(msg.sender, address(acpm), inputYes ? acpm.YES() : acpm.NO(), amountIn);
shareToken.unsafeTransferFrom(msg.sender, address(acpm), inputYes ? TokenId.getTokenId(address(acpm), 2) : TokenId.getTokenId(address(acpm), 1), amountIn);
acpm.swap(msg.sender, inputYes ? outputShares : 0, inputYes ? 0 : outputShares);
}

Expand Down
67 changes: 67 additions & 0 deletions solidity/contracts/AugurConstantProductShareToken.sol
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like we should fold this, the pool, and the router all into a single contract to minimize the amount of moving around assets we need to do? Or would we gain little benefit from such a combination?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea thats the next thing I plan to take a stab at once this is in. Will involve a core contract that uses delegate calls for at least some of the implementation though bc of size constraints.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
pragma solidity 0.8.29;

import { IERC20 } from "./IERC20.sol";
import { ERC1155 } from "./ERC1155.sol";
import { Constants } from "./Constants.sol";
import { TokenId } from "./TokenId.sol";
import { IOICash } from "./IOICash.sol";

contract AugurConstantProductShareToken is ERC1155 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually need to be an ERC1155? I think the minimum requirement is that a user can "roll their keys", but I don't know if we need full 1155 support? I'm not sure if it helps us to remove ERC-1155 dependency though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably doesn't need the full ERC1155 but I think we wouldn't gain much by ripping out functionality. There shouldn't be much overhead of those features in its operation within our system. Its a good mental construct too for understanding how the system is operating and what state looks like.


IERC20 public dai = IERC20(Constants.DAI_ADDRESS);
IOICash public constant cash = IOICash(Constants.AUGUR_OICASH);
uint256 public constant numTicks = 1000;

constructor() {
dai.approve(Constants.AUGUR_ADDRESS, 2**256-1);
}

function buyCompleteSets(address acpm, address account, uint256 setsToBuy) external {
uint256 amountInDai = setsToBuy * numTicks;
dai.transferFrom(msg.sender, address(this), amountInDai);
cash.deposit(amountInDai);

uint256[] memory tokenIds = new uint256[](3);
uint256[] memory values = new uint256[](3);

tokenIds[0] = TokenId.getTokenId(acpm, 0);
tokenIds[1] = TokenId.getTokenId(acpm, 1);
tokenIds[2] = TokenId.getTokenId(acpm, 2);
values[0] = setsToBuy;
values[1] = setsToBuy;
values[2] = setsToBuy;

_mintBatch(account, tokenIds, values, bytes(""), false);
}

function sellCompleteSets(address acpm, address holder, address recipient, uint256 setsToSell) external {
require(holder == msg.sender || isApprovedForAll(holder, msg.sender) == true, "ERC1155: need operator approval to sell complete sets");

uint256[] memory tokenIds = new uint256[](3);
uint256[] memory values = new uint256[](3);

tokenIds[0] = TokenId.getTokenId(acpm, 0);
tokenIds[1] = TokenId.getTokenId(acpm, 1);
tokenIds[2] = TokenId.getTokenId(acpm, 2);
values[0] = setsToSell;
values[1] = setsToSell;
values[2] = setsToSell;

_burnBatch(holder, tokenIds, values, bytes(""), false);

cash.withdraw(setsToSell * numTicks);
dai.transfer(recipient, dai.balanceOf(address(this)));
}

function unsafeTransferFrom(address _from, address _to, uint256 _id, uint256 _value) public {
_transferFrom(_from, _to, _id, _value, bytes(""), false);
}

function unsafeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _values) public {
_batchTransferFrom(_from, _to, _ids, _values, bytes(""), false);
}

function onBurn(uint256 _tokenId, address _target, uint256 _amount) internal override {}
function onMint(uint256 _tokenId, address _target, uint256 _amount) internal override {}
function onTokenTransfer(uint256 _tokenId, address _from, address _to, uint256 _value) internal override {}
}
1 change: 1 addition & 0 deletions solidity/contracts/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity 0.8.29;

library Constants {
address constant AUGUR_ADDRESS = 0x23916a8F5C3846e3100e5f587FF14F3098722F5d;
address constant AUGUR_OICASH = 0xd2486eD7Fdb3F2325d95e4E648F2c92aA2948fF5;
address constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address constant SHARE_TOKEN = 0x9e4799ff2023819b1272eee430eadf510eDF85f0;
uint48 constant YEAR_2099 = 4080321626;
Expand Down
Loading