Implement exponential decay function in Solidity - ethereum

I am working on an ERC-721 token in which there is a built-in royalty feature, i.e., when a user issues a token, he chooses the royalty fee in ETH and he is entitled to that fee on every secondary market trade. Also, the more tokens a user holds belonging to a specific issuer, the lower the royalty fees and I need to implement an exponential decay function to calculate the discount.
M = e ^ (-x / 50)
where, e is Euler's number
so the discountedFee = M * standardFee
For instance, the royalty fee of a token is 0.01 ETH and in the contract, it will be represented as 10000000000000000 WEI. Now the buyer holds 10 tokens of that issuer and now he is about to buy his 11th token, so the royalty fee will be calculated as
M = e ^ (-10 / 50) = 0.818
discountedFee = M * standardFee = 0.0081 ETH
Can this function in Solidity? Thanks in advance.

Implementing a true exponential decay in solidity is pretty difficult, let alone expensive since the language doesn't support floating point arithmetic. This link might be helpful.
Personally, I approximate an exponential decay with a series of linear functions targeting a half-life in a certain time-period.
function getPrice(uint256 value, uint256 t, uint256 halfLife) public pure returns (uint256 price) {
value >>= (t / halfLife);
t %= halfLife;
price = value - value * t / halfLife / 2;
}
Half-life in my code was the number of seconds in two days.

Related

Is this a correct way to calculate price for tokens with different decimals?

I've tested this function on a few different examples and it seems to return proper numbers but I'm not sure if this is correct and will work for all cases (different decimals of A and B, different values of amountOfB and priceForA etc).
Is this the right way to compute price of token A that have fixed price in amount of B tokens?
function calculatePrice(uint8 decimalsA, uint256 priceForA, uint256 amountOfB) public {
return amountOfB * 10 ** decimalsA / priceForA;
}
Decimals for both tokens A and B can be equal or one can be higher or lower than the other.
For example
decimalsA = 6;
decimalsB = 18;
// priceForA is price for 1 (human readable) A token which is 0.01 (human readable) B tokens
// so in other words for 1000000 A you need to pay 10000000000000000 B
priceForA = 10000000000000000;
// this is the amount of B tokens that will be used to buy A tokens
amountOfB = 50000000000000000;
In this example we should get 5000000 as return value so 5A (human readable) tokens.

Gas efficiency of totalSupply() vs. a tokenID counter | ERC-721

I'm creating a solidity contract for an NFT and in my mint function I'm not sure if a call to totalSupply() vs using a token counter and incrementing it is better practice. Does either variation cost more gas? Is one the more standard practice? I've seen examples of both being used.
Variation 1:
contract MyNFT is ERC721Enumerable, PaymentSplitter, Ownable {
using Counters for Counters.Counter;
Counters.Counter private currentTokenId;
...
function mint(uint256 _count)
public payable
{
uint256 tokenId = currentTokenId.current();
require(tokenId < MAX_SUPPLY, "Max supply reached");
for(uint i = 0; i < _count; ++i){
currentTokenId.increment();
uint256 newItemId = currentTokenId.current();
_safeMint(msg.sender, newItemId);
}
}
}
Variation 2:
function mint(uint256 _count)
public payable
{
uint supply = totalSupply();
require( supply + _count <= MAX_SUPPLY, "Exceeds max supply." );
for(uint i = 0; i < _count; ++i){
_safeMint(msg.sender, supply + i);
}
}
Both versions seem to work. I just want to be sure I'm using the most efficient / secure. Thanks for any advice!
First off all, you need to show us the underlying implementations. However, I can speculate that these are unmodified openzeppelin implementations for ERC721Enumerable and Counters.
For your case only, using Counter seems a little bit pointless to me.
It increases your deployment costs(just a little bit) because of redundant code coming from Counter library
You already know the length of your tokens array, why keep it twice? Counters is created for situations where you don't know the number of elements, like a mapping.
I am not guaranteeing correctness of the following analysis
Calling totalSupply (looking from opcode point of view) will:
jump to totalsupply (8 gas)
sload tokens.slot (200) gas
However, while using Counter, you sstore (>= 5000 gas) each time you decrement and sload (200 gas) each time you read.
As long as i am not mistaken about Counter using storage, and therefore sstore and sload opcodes, second variant will use much less gas.

Need help understanding solidity contract function swapback()

I have recently been delving into the code of a few contracts and have been commenting them myself to try and understand how they work as the entire field seems ironically black-box from a development perspective with most code copy and pasted from other contracts with 0 comments explaining what functions do.
As such I have seen a recurring function across multiple projects which I am struggling to get my head around. Namely shouldswapback() and swapback().
shouldSwapBack():
function shouldSwapBack() internal view returns (bool) {
return msg.sender != pair
&& !inSwap
&& swapEnabled
&& _balances[address(this)] >= swapThreshold;
}
My understanding of above is to return true only when the caller of the function is not the LP address, swapping is allowed and we are not in a swap currently and the balance of the contract is >= the predefined swap threshold.
2 questions regarding this:
1. In what context/circumstance is the msg.sender the LP contract?
2. Why is it important to only swap if the contract balance is greater than a certain percentage? What would be the effect if this was 0?
The actual swap function is longer:
function swapBack() internal swapping {
uint256 contractTokenBalance = balanceOf(address(this));
/* (contract balance * 3)/15/2 */
uint256 amountToLiquify = contractTokenBalance.mul(liquidityFee).div(totalFee).div(2);
uint256 amountToSwap = contractTokenBalance.sub(amountToLiquify);
/* set the address path for the BNB and Token addresses */
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = WBNB;
/* note before balance of contract */
uint256 balanceBefore = address(this).balance;
/*swap tokens using PCS */
router.swapExactTokensForETHSupportingFeeOnTransferTokens(
amountToSwap, /*The amount of input tokens to send. */
0, /*The minimum amount of output tokens that must be received for the transaction not to revert. */
path, /*An array of token addresses. path.length must be >= 2. Pools for each consecutive pair of addresses must exist and have liquidity. */
address(this), /*Recipient of the BNB. */
block.timestamp /*Unix timestamp after which the transaction will revert. (Think set to one block?)*/
);
uint256 amountBNB = address(this).balance.sub(balanceBefore); /* get amount swapped by checking new contract balance vs beforeswap */
uint256 totalBNBFee = totalFee.sub(liquidityFee.div(2));
uint256 amountBNBLiquidity = amountBNB.mul(liquidityFee).div(totalBNBFee).div(2);
uint256 amountBNBMarketing = amountBNB.mul(marketingFee).div(totalBNBFee);
/* send marketing fee to marketing wallet */
(bool MarketingSuccess, /* bytes memory data */) = payable(marketingFeeReceiver).call{value: amountBNBMarketing, gas: 30000}("");
require(MarketingSuccess, "receiver rejected ETH transfer");
/* if we meet the liquidity threshold add to marketwallet */
if(amountToLiquify > 0){
router.addLiquidityETH{value: amountBNBLiquidity}(
address(this),
amountToLiquify,
0,
0,
marketingFeeReceiver,
block.timestamp
);
emit AutoLiquify(amountBNBLiquidity, amountToLiquify);
}
}
My understanding for this is that the contract tokens are swapped to BNB and then the fees are calculated and sent to the appropriate wallet. However with this I don't understand:
1.Why are the tokens going the contract and not the LP?
2.If all the tokens are swapped to ETH do none get swapped back?
3.What is the significance of adding to the LP, why would this be necessary? (if this wasn't done what would the affect be on the token? I have seen projects fail in the past because they didn't buyback liquidity but I'm struggling to understand what it would do apart from ease volatility?)
I understand that this a lengthy question and my issue seems to be less from a code perspective and more from a key concept one but if anyone could help me it would be great. I have looked online for courses but none seem to really delve into dex interactions, a lot just seem very basic token/NFT creation I haven't been able to find one tutorial on making a contract that has a working tax implementation so instead I've just been cross referencing a bunch of different contracts.
If anyone can point me in the right direction for a course or even better a tutor that would be great.
Thanks.

How to Avoid making time-based decisions in contract business logic?

I wrote a Money-saving smart contract where users deposit ETH and define the amount of time they want to keep that ETH in the contract, EX: USER X deposits 2ETH for one year, they can only withdraw their ETH after that period.
But solidity linter keeps telling me that I should not rely on block.timestamp to make decisions.
This is the Saving struct I'm using to map every address to a balance and endTime:
struct Saving {
uint256 balance;
uint256 endTime;
}
Here is my function modifier where I require the withdrawal time to be greater than the endTime I stored at the deposit moment:
modifier onlyValidTimeWithdraw() {
require(
block.timestamp > balances[msg.sender].endTime,
"You cannot withdraw yet"
);
_;
}
This is the message I get through the linter.
After doing some research I found that I should not have time-dependent conditions in my contract since miners can manipulate timestamps, but I did not find any alternative to this.
Miners can manipulate block timestamp to an extent of approx. few seconds, which is enough to affect business logic depending on a second-level precision.
function betLottery() external {
// the block.timestamp can be affected by a miner
// and they can submit their own winning transaction
if (block.timestamp % 2 == 0) {
win();
}
}
But since your logic depends on much longer period, I'd simply ignore or suppress the warning.
Or if it fits your usecase, you can validate against the block number, which most linters allow.
struct Saving {
uint256 balance;
uint256 endBlock;
}
require(
block.number > balances[msg.sender].endBlock,
"You cannot withdraw yet"
);

ERC20 Token: What is address(0)? And best practices for initial token distribution?

I have a pretty boilerplate test token that I'm going to use to support a DApp project. Key functions I have questions regarding are as follows:
constructor() {
name = "Test Token";
symbol = "TTKN";
decimals = 18;
_totalSupply = 1000000000000000000000000000000;
//WITHOUT DECIMALS = 1,000,000,000,000; should be 1 trillion
balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function totalSupply() public override view returns (uint256) {
return _totalSupply - balances[address(0)];
}
First, a quick question about decimals and supply: did I set this up correctly to create 1 trillion of the TTKN token? And do I really need so many decimal places?
Second, what exactly is address(0)? My understanding of the constructor is that address(0) first transfers all the tokens to msg.sender, which is me, the person who deploys this contract.
And finally, what are the best practices for initially distributing the tokens? What I want is basically as follows:
a) Myself and a few other devs each get 1% of the initial supply
b) Our DApp, a separate smart contract, will get 50% of the initial supply, and will use this to reward users for interacting with our website/project
c) To accomplish a) and b), me, the contract deployer, should manually transfer these tokens as planned?
d) The rest of the coins... available to go on an exchange somehow (maybe out of scope of question)
So now that I've deployed this test token on remix and am getting a feel for how to transfer around the tokens, I want to understand the above points in relation to our project. Is my plan generally acceptable and feasible, and is it the case that as the initial owner I'm just making a bunch of transfer calls on the ETH mainnet eventually when I deploy?
did I set this up correctly to create 1 trillion of the TTKN token?
This is one of the correct ways. More readable would be also:
_totalSupply = 1000000000000 * 1e18;
or
// 10 to the power of
_totalSupply = 1000000000000 * (10 ** decimals);
^^ mind that this snippet performs a storage read (of the decimals variable) so it's more expensive gas-wise
a well as
_totalSupply = 1000000000000 ether;
^^ using the ether unit, an alias for * 1e18
what exactly is address(0)
If it's in the first param of the Transfer event, it means the tokens are minted. If it's in the second param, it means a burn of the tokens.
A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created.
Source: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1
initially distributing the tokens
You can perform the distribution in the constructor. For the sake of simplicity, my example shows the "exchange" as a regular address managed by your team that will send the tokens to the exchange manually. But it's possible to list a token on a DEX automatically as well.
_totalSupply = 1000000000000 * 1e18;
address[3] memory devs = [address(0x123), address(0x456), address(0x789)];
address dapp = address(0xabc);
address exchange = address(0xdef);
// helper variable to calculate the remaining balance for the exchange
uint256 totalSupplyRemaining = _totalSupply;
// 1% for each of the devs
uint256 devBalance = _totalSupply / 100;
for (uint i = 0; i < 3; i++) {
balances[devs[i]] = devBalance;
emit Transfer(address(0x0), devs[i], devBalance);
totalSupplyRemaining -= devBalance;
}
// 50% for the DApp
uint256 dappBalance = _totalSupply / 2;
balances[dapp] = dappBalance;
emit Transfer(address(0x0), dapp, dappBalance);
totalSupplyRemaining -= dappBalance;
// the rest for the exchange
balances[exchange] = totalSupplyRemaining;
emit Transfer(address(0x0), exchange, totalSupplyRemaining);