Smart Contract Implementation II
Implementing the sell function
Now let us talk about the sell()
function.
The sell()
function
The sell()
function determines the sell price after the MFG is reached. sell
calculates the amount of tez for the sent number of tokens when a user wants to sell the offering's tokens.

The sell price is lower than the buy price.
If you are wondering why the sell price is lower than the buy price, make sure to go through the basics in the Continuous Organizations Whitepaper.
Entrypoint
Let us have a look at the sell
entrypoint:
@sp.entry_point
def sell(self, params):
sp.if self.data.phase == 0:
self.sell_initial(params.amount)
sp.if self.data.phase == 1:
self.sell_slope(params.amount)
This seems less complex than the buy entrypoint. The reason is that we cannot change the phase by selling:
def sell_initial(self, amount):
# check if the address owns tokens
sp.if self.data.ledger.contains(sp.sender):
# check if the address owns enough tokens
sp.if self.data.ledger[sp.sender] >= sp.as_nat(amount):
# calculate the amount of tez to send
# see https://github.com/C-ORG/whitepaper#-investments---sell
pay_amount = sp.local(
"pay_amount",
sp.utils.mutez_to_nat(self.data.price) * sp.as_nat(amount)
)
# burn the amount of tokens sold
self.burn_intern(amount)
# send pay_amount tez to the sender of the transaction
sp.send(sp.sender, sp.utils.nat_to_mutez(pay_amount.value))
self.modify_sell_slope(sp.utils.nat_to_mutez(pay_amount.value))
In the initial phase, one can sell a token and get 100% of the buy price, which remains the same until the MFG is reached. We do not use the sell_slope
in the initial phase, because the price is fixed but we keep it updated.
In the post-MFG phase, we use the sell_slope
to calculate the amount of tez to be sent:
def sell_slope(self, amount):
# check if the address owns tokens
sp.if self.data.ledger.contains(sp.sender):
# check if the address owns enough tokens
sp.if self.data.ledger[sp.sender] >= sp.as_nat(amount):
# calculate the amount of tez to send
# see https://github.com/C-ORG/whitepaper#-investments---sell
pay_amount = sp.local(
"pay_amount",
sp.as_nat(
self.data.total_tokens * sp.as_nat(amount) * self.data.sell_slope -
sp.as_nat(amount * amount) * self.data.sell_slope / 2
) +
self.data.sell_slope * sp.as_nat(amount) *
self.data.burned_tokens * self.data.burned_tokens /
sp.as_nat(2 * (self.data.total_tokens - self.data.burned_tokens) )
)
# burn the amount of tokens sold
self.burn_intern(amount)
# send pay_amount tez to the sender of the transaction
sp.send(sp.sender, sp.utils.nat_to_mutez(pay_amount.value))
self.modify_sell_slope(sp.utils.nat_to_mutez(pay_amount.value))
The calculation of pay_amount
looks a bit messy because it depends on total_tokens
, self.data.sell_slope
, params.amount
(the amount of tez sent with the transaction), and self.data.burned_tokens
:

In the formula, our pay_amount
represents M
, x
is the amount of tez sent with the transaction, x'
is the number of burned tokens, and a
represents the total number of tokens issued.
As you can see, the sell slope increases if tokens are burned but the buy slope is always constant. This means that the entrypoint burn
allows for the organization to increase the sell price.
You can see that the contract burn
s the number of tokens sold.
Calculating the sell slope
Compared to the buy price calculation, this time it is a bit more complex because we do not have a linear price function for the sell price. The sell slope changes over time. The sell slope depends on the reserve, which is represented by the contract balance:

a
represents the total number of tokens issued.
# s calculus after each transaction
def modify_sell_slope(self, send_back):
sp.if self.data.total_tokens != 0:
self.data.sell_slope = 2 * sp.utils.mutez_to_nat(sp.balance - send_back) / (self.data.total_tokens * self.data.total_tokens)
The value of sp.balance
is the balance of the contract after the transaction is received but before the contract sends any tez back to the user. So the helper function needs to know the number of tez sent back.
Burning tokens
Because burning is a part of the selling process, we define an internal function to burn tokens:
# internal burn function will be called by the entrypoints burn and sell
def burn_intern(self, amount):
# check if the address owns tokens
sp.if self.data.ledger.contains(sp.sender):
# check if the address owns enough tokens
sp.if self.data.ledger[sp.sender] >= sp.as_nat(amount):
# "burn"
self.data.ledger[sp.sender] = sp.as_nat(self.data.ledger[sp.sender] - sp.as_nat(amount))
self.data.burned_tokens += sp.as_nat(amount)
In addition, the contract offers a burn
entrypoint, in case a token holder wants to burn its tokens:
@sp.entry_point
def burn(self, params):
self.burn_intern(params.amount)
self.modify_sell_slope()
In the above code, an important aspect to notice is that we do not modify the total number of tokens issued if we burn a token. Thus, the buy price is not affected.
Anyway, there is another mechanism by which users/investors can gain profits from buying tokens: closing.
The closing phase
If you want to again take a look at the closing phase, the introduction section of this chapter includes a section on Stages in a Rolling SAFE.

If the closing phase is triggered, the contract buys back each token for the price of the last buy price and pays the token owners their part of the exit fee:
@sp.entry_point
def close(self):
# check if MPT is reached
sp.verify(sp.now - self.data.MPT >= 0)
# check that the initial phase is over but not closed
sp.verify(self.data.phase == 1)
# verify this is called by the org
sp.verify(sp.sender == self.data.organization)
# check the correct amount of tez is sent
closing_sell_price= sp.local(
"closing_sell_price",
self.data.b * self.data.total_tokens
)
closing_sell_amount= sp.local(
"closing_sell_amount",
closing_sell_price.value * sp.as_nat(self.data.total_tokens - self.data.burned_tokens)
)
sp.if sp.balance < sp.utils.nat_to_mutez(closing_sell_amount.value):
sp.failwith("Please send more tez for the closing")
sp.for account in self.data.ledger.items():
sp.send(account.key, sp.utils.nat_to_mutez(account.value * closing_sell_price.value))
self.data.phase = 2
So, the sum of the payments is the exit fee.
Only the contract owner can call close
. For the close
transaction to be successful, the contract owner needs to send the exit fee. Note that the minimum period of time (MPT) must be reached if the organization wants to trigger the closing.
Notice that if the data in the ledger increases in size, closing costs more beacuse of:
sp.for account in self.data.ledger.items():
sp.send(account.key, sp.utils.nat_to_mutez(account.value * closing_sell_price.value))
We do not expect this to be a problem because one needs to pay tez to increase the size of the ledger. Alternatively we can create another entrypoint which can be called by the investors to get their tez.