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
108 changes: 108 additions & 0 deletions contracts/BondCurveStep.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@compiler >=4.2

include "Frac.aes"

contract BondCurveStep =

/**
* Alpha here is used to define the step of the curve
* and will determine the ratios for
* the reserve and the spread
*
* Adjust the second argument of Frac.make_frac()
* according to your fungible token decimals
*
* Example:
* - Frac.make_frac(1, 1) would mean we are working with
* the lowest possible integers.
* - Frac.make_frac(1, 1000000000000000000) - this config
* would be suitable for working with fungible tokens
* with 18 decimals.
*/
entrypoint alpha() : Frac.frac = Frac.make_frac(1, 1)

// Initial price for the buy curve
entrypoint init_buy_price() : Frac.frac = Frac.from_int(1)

// Initial price for the sell curve
entrypoint init_sell_price() : Frac.frac = Frac.from_int(0)

// Returns the current buy price
entrypoint
buy_price : int => int
buy_price(0) = Frac.round(init_buy_price())
buy_price(total_supply: int) : int =
Frac.ceil(buy_curve(Frac.from_int(total_supply)))

// Returns the current sell price
entrypoint
sell_price : int => int
sell_price(0) = Frac.round(init_sell_price())
sell_price(total_supply: int) : int =
Frac.ceil(sell_curve(Frac.from_int(total_supply)))

/**
* Calculate the AE price for amount of tokens to be bought
* based on the total supply of the corresponding FT.
*
* Simplified equation to calculate the buy_price
* given the current token supply (total_supply) and desired amount of
* tokens to be acquired (buy_tokens):
* (buy_price() + buy_curve(total_supply + buy_tokens)) * buy_tokens / 2
*/
entrypoint
calculate_buy_price : (int, int) => int
calculate_buy_price(total_supply: int, buy_tokens: int) : int =
require(total_supply >= 0, "ERROR_TOTAL_SUPPLY_NEGATIVE")
require(buy_tokens >= 0, "ERROR_BUY_TOKENS_NEGATIVE")
let buy_tokens_amount = Frac.from_int(buy_tokens)
let total_supply_frac = Frac.from_int(total_supply)
Frac.ceil(Frac.div(
Frac.mul(
(Frac.add( Frac.from_int(buy_price(total_supply))
, buy_curve(Frac.add( total_supply_frac
, buy_tokens_amount))))
, buy_tokens_amount
) , Frac.from_int(2)))

/**
* Returns the amount of AE tokens to be received
* when selling fungible tokens back to the curve.
*
* Simplified equation to calculate the return amount
* given the current token supply (total_supply) and desired amount of
* tokens to be sold back to the curve (sell_tokens):
* (sell_price() + sell_curve(total_supply - sell_tokens)) * sell_tokens / 2
*/
entrypoint
calculate_sell_return : (int, int) => int
calculate_sell_return(total_supply: int, sell_tokens: int) : int =
require(total_supply >= 0, "ERROR_TOTAL_SUPPLY_NEGATIVE")
require(sell_tokens >= 0, "ERROR_SELL_TOKENS_NEGATIVE")
require(total_supply >= sell_tokens, "ERROR_SELL_INSUFFICIENT_TOTAL_SUPPLY")
let sell_tokens_amount = Frac.from_int(sell_tokens)
let total_supply_frac = Frac.from_int(total_supply)
Frac.ceil(Frac.div(
Frac.mul(
(Frac.add( Frac.from_int(sell_price(total_supply))
, sell_curve(Frac.sub( total_supply_frac
, sell_tokens_amount))))
, sell_tokens_amount
) , Frac.from_int(2)))

// Step Buy curve formula
entrypoint buy_curve(x: Frac.frac) : Frac.frac =
require(Frac.is_sane(x), "INVALID_FRAC_NUMBER")
Frac.from_int(Frac.ceil(Frac.mul(x, alpha())))

// Step Sell curve formula
entrypoint sell_curve(x: Frac.frac) : Frac.frac =
require(Frac.is_sane(x), "INVALID_FRAC_NUMBER")
Frac.sub(
Frac.from_int(
Frac.floor(
Frac.mul( x
, alpha() )
)
)
, Frac.from_int(1))
19 changes: 19 additions & 0 deletions contracts/BondCurveStepInterface.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@compiler >=4.2

/**
* Step Bonding Curve Interface
*/
contract BondCurveStepInterface =
// Returns the current buy price
entrypoint buy_price : (int) => int

// Returns the current sell price
entrypoint sell_price : (int) => int

// Returns the AE price for the desired amount
// of tokens to be bought from the curve.
entrypoint calculate_buy_price : (int, int) => int

// Returns the amount of AE tokens to be received back
// when selling fungible tokens back to the curve.
entrypoint calculate_sell_return : (int, int) => int
8 changes: 4 additions & 4 deletions test/bondCurveLinearTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('Bonding Curve Contract', () => {

describe('Buy current price tests', () => {
it.each(
[...testData],
[...testData.linear],
'Should get buy price for supply %s',
['element'],
(p, next) => {
Expand All @@ -76,7 +76,7 @@ describe('Bonding Curve Contract', () => {

describe('Sell current price tests', () => {
it.each(
[...testData],
[...testData.linear],
'Should get sell price for supply %s',
['element'],
(p, next) => {
Expand All @@ -94,7 +94,7 @@ describe('Bonding Curve Contract', () => {

describe('Calculate Buy price tests', () => {
it.each(
[...testData],
[...testData.linear],
'Should calculate buy price for supply %s',
['element'],
(p, next) => {
Expand All @@ -114,7 +114,7 @@ describe('Bonding Curve Contract', () => {

describe('Sell price tests', () => {
it.each(
[...testData],
[...testData.linear],
'Should calculate sell return for supply %s',
['element'],
(p, next) => {
Expand Down
160 changes: 160 additions & 0 deletions test/bondCurveStepTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* ISC License (ISC)
* Copyright (c) 2018 aeternity developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/

require('it-each')({ testPerIteration: true });
const { Universal, MemoryAccount, Node } = require('@aeternity/aepp-sdk');
const BONDING_CURVE_STEP_CONTRACT = utils.readFileRelative(
'./contracts/BondCurveStep.aes',
'utf-8',
);
const testData = require('./data');

const config = {
url: 'http://localhost:3001/',
internalUrl: 'http://localhost:3001/',
compilerUrl: 'http://localhost:3080',
};

describe('Bonding Curve Step Contract', () => {
let client, contract;

before(async () => {
client = await Universal({
nodes: [
{
name: 'devnetNode',
instance: await Node(config),
},
],
accounts: [
MemoryAccount({
keypair: wallets[0],
}),
],
networkId: 'ae_devnet',
compilerUrl: config.compilerUrl,
});
});

it('Deploying Bond Contract', async () => {
contract = await client.getContractInstance(BONDING_CURVE_STEP_CONTRACT);
const init = await contract.methods.init();
assert.equal(init.result.returnType, 'ok');
});

// describe('Buy current price tests', () => {
// it.each(
// [...testData.step],
// 'Should get buy price for supply',
// ['element'],
// (p, next) => {
// contract.methods.buy_price(p.totalSupply).then((result) => {
// assert.equal(
// result.decodedResult,
// p.totalSupply + 1,
// `Buy price incorrect for supply: ${JSON.stringify(p)}`,
// );
// next();
// });
// },
// );
// });

// describe('Sell current price tests', () => {
// it.each(
// [...testData.step],
// 'Should get sell price for supply',
// ['element'],
// (p, next) => {
// contract.methods.sell_price(p.totalSupply).then((result) => {
// assert.equal(
// result.decodedResult,
// p.totalSupply,
// `Sell price incorrect for supply: ${JSON.stringify(p)}`,
// );
// next();
// });
// },
// );
// });

describe('Calculate Buy price tests', () => {
it.each(
[...testData.step],
'Should calculate buy price for supply',
['element'],
(p, next) => {
contract.methods
.calculate_buy_price(p.totalSupply, p.buy.amount)
.then((result) => {
assert.equal(
result.decodedResult,
p.buy.aettos,
`Calculation for buy price incorrect for: ${JSON.stringify(p)}`,
);
next();
});
},
);
});

describe('Sell price tests', () => {
it.each(
[...testData.step],
'Should calculate sell return for supply',
['element'],
(p, next) => {
if (p.totalSupply >= p.sell.amount) {
contract.methods
.calculate_sell_return(p.totalSupply, p.sell.amount)
.then((result) => {
assert.equal(
result.decodedResult,
p.sell.aettos,
`Calculation for sell price incorrect for: ${JSON.stringify(
p,
)}`,
);
next();
});
} else {
contract.methods
.calculate_sell_return(p.totalSupply, p.sell.amount)
.then((result) => {
assert.equal(
result.decodedResult,
p.sell.aettos,
`Calculation for sell price incorrect for: ${JSON.stringify(
p,
)}`,
);
next();
})
.catch((e) => {
if (
e.decodedError.indexOf(
'ERROR_SELL_INSUFFICIENT_TOTAL_SUPPLY' > -1,
)
) {
next();
}
});
}
},
);
});
});
Loading