@gooddollar/goodcontracts
Version:
GoodDollar Contracts
543 lines (499 loc) • 23 kB
JavaScript
const MarketMaker = artifacts.require("GoodMarketMaker");
const GoodDollar = artifacts.require("GoodDollar");
const Bancor = artifacts.require("BancorFormula");
const Identity = artifacts.require("Identity");
const Formula = artifacts.require("FeeFormula");
const DAIMock = artifacts.require("DAIMock");
const cDAIMock = artifacts.require("cDAIMock");
const avatarMock = artifacts.require("AvatarMock");
const BN = web3.utils.BN;
export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
contract("GoodMarketMaker - calculate gd value at reserve", ([founder, staker]) => {
let goodDollar, identity, formula, marketMaker, dai, cDAI, avatar, bancor;
before(async () => {
dai = await DAIMock.new();
[cDAI, identity, formula] = await Promise.all([
cDAIMock.new(dai.address),
Identity.new(),
Formula.new(0)
]);
goodDollar = await GoodDollar.new(
"GoodDollar",
"GDD",
"0",
formula.address,
identity.address,
NULL_ADDRESS
);
avatar = await avatarMock.new("", goodDollar.address, NULL_ADDRESS);
marketMaker = await MarketMaker.new(
avatar.address,
999388834642296,
1e15
);
});
it("should initialize a token with 0 ratio and the ratio should calculate as 100% by default", async () => {
let dai1 = await DAIMock.new();
await marketMaker.initializeToken(
dai1.address,
"100",
"10000",
"0" //0% rr
);
const newreserveratio = await marketMaker.calculateNewReserveRatio(dai1.address);
expect(newreserveratio.toString()).to.be.equal("999388"); // result of initial expansion rate * 100% ratio
});
it("should initialize a token with 0 ratio and the ratio should be updated as it was 100% by default", async () => {
let dai1 = await DAIMock.new();
await marketMaker.initializeToken(
dai1.address,
"100",
"10000",
"0" //0% rr
);
await marketMaker.expandReserveRatio(dai1.address);
const rr = await marketMaker.reserveTokens(dai1.address);
expect(rr["1"].toString()).to.be.equal("999388"); // result of initial expansion rate * 100% ratio
});
it("should initialize token with price", async () => {
const expansion = await marketMaker.initializeToken(
cDAI.address,
"100", //1gd
"10000", //0.0001 cDai
"1000000" //100% rr
);
const price = await marketMaker.currentPrice(cDAI.address);
expect(price.toString()).to.be.equal("10000"); //1gd is equal 0.0001 cDAI = 100000 wei;
const onecDAIReturn = await marketMaker.buyReturn(
cDAI.address,
"100000000" //1cDai
);
expect(onecDAIReturn.toNumber() / 100).to.be.equal(10000); //0.0001 cdai is 1 gd, so for 1eth you get 10000 gd (divide by 100 to account for 2 decimals precision)
});
it("should update reserve ratio by yearly rate", async () => {
const expansion = await marketMaker.reserveRatioDailyExpansion();
// 20% yearly. set up in the constructor
expect(expansion.toString()).to.be.equal("999388834642296000000000000");
await marketMaker.expandReserveRatio(cDAI.address);
const daytwoRR = await marketMaker.reserveTokens(cDAI.address);
// after interval expansion
expect(daytwoRR["1"].toString()).to.be.equal("999388");
await marketMaker.expandReserveRatio(cDAI.address);
const daythreeRR = await marketMaker.reserveTokens(cDAI.address);
// after interval expansion
expect(daythreeRR["1"].toString()).to.be.equal("998777");
});
it("should calculate mint UBI correctly for 8 decimals precision", async () => {
const gdPrice = await marketMaker.currentPrice(cDAI.address);
const toMint = await marketMaker.calculateMintInterest(cDAI.address, "100000000");
const expectedTotalMinted = 10 ** 8 / gdPrice.toNumber(); //1cdai divided by gd price;
expect(expectedTotalMinted).to.be.equal(10000); //1k GD since price is 0.0001 cdai for 1 gd
expect(toMint.toString()).to.be.equal((expectedTotalMinted * 100).toString()); //add 2 decimals precision
});
it("should not return a sell contribution if the given gd is less than the given contribution amount", async () => {
const marketMaker1 = await MarketMaker.new(
avatar.address,
999388834642296,
1e15
);
await marketMaker1.initializeToken(
dai.address,
"100", //1gd
web3.utils.toWei("0.0001", "ether"), //0.0001 dai
"800000" //80% rr
);
const error = await marketMaker1
.sellWithContribution(dai.address, web3.utils.toWei("1", "ether"), web3.utils.toWei("2", "ether")).catch(e => e);
expect(error.message).to.have.string("GD amount is lower than the contribution amount");
});
it("should not return a sell contribution if the given gd is less than the the total supply", async () => {
const marketMaker1 = await MarketMaker.new(
avatar.address,
999388834642296,
1e15
);
await marketMaker1.initializeToken(
dai.address,
"100", //1gd
web3.utils.toWei("0.0001", "ether"), //0.0001 dai
"800000" //80% rr
);
const error = await marketMaker1
.sellWithContribution(dai.address, web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether")).catch(e => e);
expect(error.message).to.have.string("GD amount is higher than the total supply");
});
it("should be able to calculate and update bonding curve gd balance based on oncoming cDAI and the price stays the same", async () => {
const priceBefore = await marketMaker.currentPrice(cDAI.address);
await marketMaker.mintInterest(cDAI.address, web3.utils.numberToHex(1e8));
expect(
Math.floor((await marketMaker.currentPrice(cDAI.address)) / 100).toString()
).to.be.equal(Math.floor(priceBefore.toNumber() / 100).toString());
});
it("should not be able to mint interest by a non owner", async () => {
let error = await marketMaker
.mintInterest(
cDAI.address,
web3.utils.numberToHex(1e8),
{
from: staker
}).catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should not be able to mint expansion by a non owner", async () => {
let error = await marketMaker
.mintExpansion(
cDAI.address,
{
from: staker
}).catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should mint 0 gd tokens if the add token supply is 0", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyBefore = reserveTokenBefore.gdSupply;
await marketMaker.mintInterest(cDAI.address, "0");
let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyAfter = reserveTokenAfter.gdSupply;
expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
});
it("should be able to update the reserve ratio only by the owner", async () => {
let error = await marketMaker
.expandReserveRatio(cDAI.address, {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should be able to update the bonding curve only by the owner", async () => {
let error = await marketMaker
.mintInterest(cDAI.address, web3.utils.numberToHex(1e8), {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should be able to update the bonding curve only by the owner", async () => {
let error = await marketMaker
.mintExpansion(cDAI.address, web3.utils.numberToHex(1e8), {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should be able to calculate minted gd based on expansion of reserve ratio, the price stays the same", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let reserveRatioBefore = reserveTokenBefore.reserveRatio;
const priceBefore = await marketMaker.currentPrice(cDAI.address);
const toMint = await marketMaker.calculateMintExpansion(cDAI.address);
expect(toMint.toString()).not.to.be.equal("0");
const newRR = await marketMaker.calculateNewReserveRatio(cDAI.address);
expect(reserveRatioBefore.toString()).not.to.be.equal(newRR.toString());
const priceAfter = await marketMaker.currentPrice(cDAI.address);
expect(priceAfter.toString()).to.be.equal(priceBefore.toString());
});
it("should be able to calculate and update gd supply based on expansion of reserve ratio, the price stays the same", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyBefore = reserveTokenBefore.gdSupply;
let reserveRatioBefore = reserveTokenBefore.reserveRatio;
const priceBefore = await marketMaker.currentPrice(cDAI.address);
await marketMaker.mintExpansion(cDAI.address);
let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyAfter = reserveTokenAfter.gdSupply;
let reserveRatioAfter = reserveTokenAfter.reserveRatio;
const priceAfter = await marketMaker.currentPrice(cDAI.address);
expect(priceAfter.toString()).to.be.equal(priceBefore.toString());
expect(gdSupplyBefore.toString()).not.to.be.equal(gdSupplyAfter.toString());
expect(reserveRatioBefore.toString()).not.to.be.equal(reserveRatioAfter.toString());
});
it("should have new return amount when RR is not 100%", async () => {
const expansion = await marketMaker.initializeToken(
dai.address,
"100", //1gd
web3.utils.toWei("0.0001", "ether"), //0.0001 dai
"800000" //80% rr
);
const price = await marketMaker.currentPrice(dai.address);
expect(price.toString()).to.be.equal("100000000000000"); //1gd is equal 0.0001 dai = 1000000000000000 wei;
const oneDAIReturn = await marketMaker.buyReturn(
dai.address,
web3.utils.toWei("1", "ether") //1Dai
);
//bancor formula to calcualte return
//gd return = gdsupply * ((1+tokenamount/tokensupply)^rr -1)
const expectedReturn = 1 * ((1 + 1 / 0.0001) ** 0.8 - 1);
expect(oneDAIReturn.toNumber() / 100).to.be.equal(
Math.floor(expectedReturn * 100) / 100
);
});
it("should calculate mint UBI correctly for 18 decimals precision", async () => {
const gdPrice = await marketMaker.currentPrice(dai.address);
const toMint = await marketMaker.calculateMintInterest(dai.address, web3.utils.toWei("1", "ether"));
const expectedTotalMinted = 10 ** 18 / gdPrice.toNumber();
// according to the sell formula the gd price should be 10^14 so 10^18 / 10^14 = 10^4
// Return = _reserveBalance * (1 - (1 - _sellAmount / _supply) ^ (1000000 / _reserveRatio))
expect(expectedTotalMinted).to.be.equal(10000);
expect(toMint.toString()).to.be.equal((expectedTotalMinted * 100).toString());
});
it("should calculate sell return with cDAI", async () => {
const gDReturn = await marketMaker.sellReturn(
cDAI.address,
10 //0.1 gd
);
let reserveToken = await marketMaker.reserveTokens(cDAI.address);
let reserveBalance = reserveToken.reserveSupply.toString();
let sellAmount = 10;
let supply = reserveToken.gdSupply.toString();
let rr = reserveToken.reserveRatio.toNumber();
// sell formula (as in calculateSaleReturn):
// return = reserveBalance * (1 - (1 - sellAmount / supply) ^ (1000000 / reserveRatio))
const expectedReturn =
reserveBalance * (1 - (1 - sellAmount / supply) ** (1000000 / rr));
expect(gDReturn.toNumber()).to.be.equal(Math.floor(expectedReturn));
});
it("should calculate sell return with DAI", async () => {
const gDReturn = await marketMaker.sellReturn(
dai.address,
10 //0.1 gd
);
let reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalance = reserveToken.reserveSupply.toString();
let sellAmount = 10;
let supply = reserveToken.gdSupply.toString();
let rr = reserveToken.reserveRatio.toNumber();
// sell formula (as in calculateSaleReturn):
// return = reserveBalance * (1 - (1 - sellAmount / supply) ^ (1000000 / reserveRatio))
const expectedReturn =
reserveBalance * (1 - (1 - sellAmount / supply) ** (1000000 / rr));
expect(gDReturn.toNumber()).to.be.equal(Math.floor(expectedReturn));
});
it("should be able to update balances based on buy return calculation", async () => {
let reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceBefore = reserveToken.reserveSupply;
let supplyBefore = reserveToken.gdSupply;
let rrBefore = reserveToken.reserveRatio;
let amount = web3.utils.toWei("1", "ether");
let transaction = await marketMaker.buy(
dai.address,
web3.utils.toWei("1", "ether") //1Dai
);
reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceAfter = reserveToken.reserveSupply;
let supplyAfter = reserveToken.gdSupply;
let rrAfter = reserveToken.reserveRatio;
expect(transaction.logs[0].event).to.be.equal("BalancesUpdated");
expect((reserveBalanceAfter - reserveBalanceBefore).toString()).to.be.equal(
amount.toString()
);
expect((supplyAfter - supplyBefore).toString()).to.be.equal(
transaction.logs[0].args.returnAmount.toString()
);
expect(rrAfter.toString()).to.be.equal(rrBefore.toString());
});
it("should be able to update balances based on sell return calculation", async () => {
let reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceBefore = reserveToken.reserveSupply;
let supplyBefore = reserveToken.gdSupply;
let rrBefore = reserveToken.reserveRatio;
let amount = 100;
let transaction = await marketMaker.sell(dai.address, 100);
reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceAfter = reserveToken.reserveSupply;
let supplyAfter = reserveToken.gdSupply;
let rrAfter = reserveToken.reserveRatio;
expect(transaction.logs[0].event).to.be.equal("BalancesUpdated");
expect(
reserveBalanceAfter.add(transaction.logs[0].args.returnAmount).toString()
).to.be.equal(reserveBalanceBefore.toString());
expect((supplyBefore - supplyAfter).toString()).to.be.equal(amount.toString());
expect(rrAfter.toString()).to.be.equal(rrBefore.toString());
});
it("should be able to update balances based on sell with contribution return calculation", async () => {
let reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceBefore = reserveToken.reserveSupply;
let supplyBefore = reserveToken.gdSupply;
let rrBefore = reserveToken.reserveRatio;
let amount = 100;
let transaction = await marketMaker.sellWithContribution(dai.address, 100, 80);
reserveToken = await marketMaker.reserveTokens(dai.address);
let reserveBalanceAfter = reserveToken.reserveSupply;
let supplyAfter = reserveToken.gdSupply;
let rrAfter = reserveToken.reserveRatio;
expect(transaction.logs[0].event).to.be.equal("BalancesUpdated");
expect(
reserveBalanceAfter.add(transaction.logs[0].args.returnAmount).toString()
).to.be.equal(reserveBalanceBefore.toString());
expect((supplyBefore - supplyAfter).toString()).to.be.equal(amount.toString());
expect(rrAfter.toString()).to.be.equal(rrBefore.toString());
});
it("should not be able to calculate the buy return in gd and update the bonding curve params by a non-owner account", async () => {
let error = await marketMaker
.buy(dai.address, web3.utils.toWei("1", "ether"), {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should not be able to calculate the sell return in reserve token and update the bonding curve params by a non-owner account", async () => {
let error = await marketMaker
.sell(dai.address, 100, {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should not be able to calculate the sellWithContribution return in reserve token and update the bonding curve params by a non-owner account", async () => {
let error = await marketMaker
.sellWithContribution(dai.address, 100, 80, {
from: staker
})
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should be able to buy only with active token", async () => {
let reserveToken = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyBefore = reserveToken.gdSupply;
let reserveSupplyBefore = reserveToken.reserveSupply;
let reserveRatioBefore = reserveToken.reserveRatio;
await marketMaker.initializeToken(
cDAI.address,
"0",
reserveSupplyBefore.toString(),
reserveRatioBefore.toString()
);
let error = await marketMaker
.buy(cDAI.address, web3.utils.toWei("1", "ether"))
.catch(e => e);
expect(error.message).to.have.string("Reserve token not initialized");
await marketMaker.initializeToken(
cDAI.address,
gdSupplyBefore,
reserveSupplyBefore.toString(),
reserveRatioBefore.toString()
);
});
it("should be able to sell only with active token", async () => {
let error = await marketMaker
.sell(NULL_ADDRESS, web3.utils.toWei("1", "ether"))
.catch(e => e);
expect(error.message).to.have.string("Reserve token not initialized");
});
// it("should be able to get the current price only with active token", async () => {
// await marketMaker
// .currentPrice(NULL_ADDRESS, web3.utils.toWei("1", "ether"))
// .should.be.rejectedWith("Reserve token not initialized");
// });
// it("should be able to calculate the minted interest only with active token", async () => {
// let error = await marketMaker
// .calculateMintInterest(NULL_ADDRESS, web3.utils.numberToHex(1e18))
// .catch(e => e);
// expect(error.message).to.have.string("Reserve token not initialized");
// });
// it("should be able to calculate the minted expansion only with active token", async () => {
// let error = await marketMaker
// .calculateMintExpansion(NULL_ADDRESS, web3.utils.numberToHex(1e18))
// .catch(e => e);
// expect(error.message).to.have.string("Reserve token not initialized");
// });
it("should be able to sellWithContribution only with active token", async () => {
let error = await marketMaker
.sellWithContribution(NULL_ADDRESS, web3.utils.toWei("1", "ether"), web3.utils.toWei("1", "ether"))
.catch(e => e);
expect(error.message).to.have.string("Reserve token not initialized");
});
it("should be able to sell gd only when the amount is lower than the total supply", async () => {
let reserveToken = await marketMaker.reserveTokens(cDAI.address);
let gdSupply = reserveToken.gdSupply;
let error = await marketMaker
.sell(cDAI.address, (gdSupply + 1).toString())
.catch(e => e);
expect(error.message).to.have.string("GD amount is higher than the total supply");
});
it("should set reserve ratio daily expansion", async () => {
let denom = new BN(1e15).toString();
let currentReserveRatioDailyExpansion = await marketMaker.reserveRatioDailyExpansion();
let encodedCall = web3.eth.abi.encodeFunctionCall(
{
name: "setReserveRatioDailyExpansion",
type: "function",
inputs: [
{
type: "uint256",
name: "_nom"
},
{
type: "uint256",
name: "_denom"
}
]
},
["1", denom]
);
await avatar.genericCall(marketMaker.address, encodedCall, 0);
let newReserveRatioDailyExpansion = await marketMaker.reserveRatioDailyExpansion();
expect(newReserveRatioDailyExpansion).not.to.be.equal(
currentReserveRatioDailyExpansion
);
encodedCall = web3.eth.abi.encodeFunctionCall(
{
name: "setReserveRatioDailyExpansion",
type: "function",
inputs: [
{
type: "uint256",
name: "_nom"
},
{
type: "uint256",
name: "_denom"
}
]
},
["999388834642296", denom]
);
await avatar.genericCall(marketMaker.address, encodedCall, 0);
let reserveRatioDailyExpansion = await marketMaker.reserveRatioDailyExpansion();
expect(reserveRatioDailyExpansion).not.to.be.equal(currentReserveRatioDailyExpansion);
});
it("should be able to set the reserve ratio daily expansion only by the owner", async () => {
let error = await marketMaker.setReserveRatioDailyExpansion(1, 1e15).catch(e => e);
expect(error.message).to.have.string("only Avatar can call this method");
});
it("should calculate amount of gd to mint based on incoming cDAI without effecting bonding curve price", async () => {
const priceBefore = await marketMaker.currentPrice(dai.address);
const toMint = await marketMaker.calculateMintInterest(
dai.address,
web3.utils.numberToHex(1e18),
{
from: staker
}
);
const totalMinted = 1e18 / priceBefore.toNumber();
expect(toMint.toString()).to.be.equal(Math.floor(totalMinted * 100).toString());
const priceAfter = await marketMaker.currentPrice(dai.address);
expect(priceBefore.toString()).to.be.equal(priceAfter.toString());
});
it("should not change the reserve ratio when calculate how much decrease it for the reservetoken", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let reserveRatioBefore = reserveTokenBefore.reserveRatio;
await marketMaker.calculateNewReserveRatio(cDAI.address);
let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address);
let reserveRatioAfter = reserveTokenAfter.reserveRatio;
expect(reserveRatioBefore.toString()).to.be.equal(reserveRatioAfter.toString());
});
it("should not change the gd supply when calculate how much gd to mint based on added token supply from interest", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyBefore = reserveTokenBefore.gdSupply;
await marketMaker.calculateMintInterest(cDAI.address, "100000000");
let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyAfter = reserveTokenAfter.gdSupply;
expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
});
it("should not change the gd supply when calculate how much gd to mint based on expansion change", async () => {
let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyBefore = reserveTokenBefore.gdSupply;
await marketMaker.calculateMintExpansion(cDAI.address);
let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address);
let gdSupplyAfter = reserveTokenAfter.gdSupply;
expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
});
});