Начну с того, что всем известно. ICO повсюду, все предлагают разные проекты, все продают токены. И у всех есть какая-то модель продаж со скидками, бонусами, сроками и т.п.
Как ни странно хоть область и достаточно экспериментальная, модель бонусов достаточно примитивна. Просто есть разные объемы или временные интервалы и проценты в них. Да и зачем усложнять? А я все таки попробую. Эксперимента ради. Новое всегда привлекает.
Кроме вопроса безопасности, вторым по важности в случае написания смарт-контракта является вопрос оптимизации. Каждая транзакция съедает газ, а значит и эфир, который расходуется при обработке транзакций. Если код сложный, требует много вычислений — он будет очень дорог для инвесторов в ICO.
А теперь представьте что мы хотим сделать гладкую кривую бонусов в зависимости от объема инвестированных средств. И не просто линейную, а степенную.
Bonus = (Volume of Invest ^ 0.07)-1
Вот как это выглядит на графике:
Средний инвестор по текущей статистике инвестирует около 5 ETH, так что допустим что в случае объема меньше 1 ETH бонус начисляться не будет.
Вычислять придется используя только тип uint, так как float пока достаточно экспериментален и опять же достаточно дорогой. И использовать мы можем в таком случае только умножение, деление, сложение и вычитание.
Таким образом нам нужно разложить функцию возведения в степень в ряд.
a^x=e^xlnx=1+xlna+(xlna)^2/2!+(xlna)^3/3!+? ??<x<?
В свою очередь логарифм от X надо посчитать один раз, взяв несколько ключевых точек, чтобы аппроксимировать его набором линейных функция на разных интервалах.
Дальше взять например 4 первых члена ряда.
Дальше оценить ошибку взяв нормальный калькулятор и затраченный газ за вызов функции.
В итоге вы получите что-то типа этого:
Для примера вычислил значения для приближения ln(1.07).
pragma solidity ^0.4.15;
library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
contract Simple {
using SafeMath for uint256;
uint256 m100=100000000;
uint256 ln3= 109861228;
uint256 ln10=230258512;
uint256 ln20=299573231;
uint256 ln30=340119743;
uint256 ln50=391202307;
uint256 ln100=460517025;
uint256 ln200=529831743;
uint256 ln500=621460819;
uint256 ln1000=690775527;
function test2(uint256 a){
test1(a);
}
function test1(uint256 a) constant returns (uint256 result) {
uint256 lnbase;
uint256 a0;
if(a > m100.mul(1000)){
lnbase = ln1000;
a0 = a.div(1000);
}else if(a > m100.mul(500)){
lnbase=ln500;
a0 = a.div(500);
}else if(a > m100.mul(200)){
lnbase = ln200;
a0 = a.div(200);
}else if(a > m100.mul(100)){
lnbase = ln100;
a0 = a.div(100);
}else if(a > m100.mul(50)){
lnbase = ln50;
a0 = a.div(50);
}else if(a > m100.mul(30)){
lnbase = ln30;
a0 = a.div(30);
}else if(a > m100.mul(20)){
lnbase = ln20;
a0 = a.div(20);
}else if(a > m100.mul(10)){
lnbase = ln10;
a0 = a.div(10);
}else if(a > m100.mul(3)){
lnbase = ln3;
a0 = a.div(3);
}else if(a > m100){
lnbase = 0;
a0 = a;
}else{
return a;
}
uint256 x=a0.sub(m100).mul(m100).div(a0.add(m100));
uint256 y=x.add(x.mul(x).mul(x)/m100/m100/3).mul(2);
y=lnbase.add(y);
y=y.mul(7)/100;
x=a.add(a.mul(y)/m100);
x = x.add(a.mul(y).mul(y)/m100/m100/2);
y = a.mul(y).mul(y).mul(y);
x = x.add(y/m100/m100/m100/6);
return (x);
}
}
Оценка:
9 ETH умножаем на 10^8 и передаем в функцию:
900000000 дает 1048494787, т.е. 10.485 ETH (бонус около 16%)
на калькуляторе 9^1.07 = 10,496378550818314120261545161046 ETH
ошибка: 0.001, т.е. 0.1%
С учетом затрачиваемых транзакций на перевод эта ошибка вполне сопоставима с коммисией в эфире. Если нужна большая точность, можно конечно добавить 5 член ряда.
Затраты газа на вызов этой функции:
execution cost 9598 gas (Cost only applies when called by a contract)
В случае 1001 ETH получаем:
162102425800 в сравнении с 1001^1.07=1623,54549 (т.е. бонус около 62%) это дает точность 1,0015, т.е. 0.15%
execution cost 6406 gas (Cost only applies when called by a contract)
Как вы можете видеть затраты газа на функцию небольшие (6-9к) учитывая что на данный момент все ICO рекомендуют использовать 200к газа.
И при этом точности в пределах коммисии за среднюю транзакцию.
Если вам кажется, что это какой-то конь в вакууме, то скажу что в системе бонусов проекта SINTEZ Platform sintez.global используется в расчетах дробная степень и логарифм.
quantum
А почему бы не заменить все
m100.mul(500)
на предрассчитанные константы, раз так уж взялись за оптимизацию газа?И такие штуки развернуть
y/m100/m100/m100/6
nolane
Такие вещи должен оптимизировать транслятор.
Vinchi Автор
Именно. Но по сути — с сокращениями код был бы просто менее понятен. Так то конечно он будет еще оптимизирован. Я просто хотел показать, что вопреки мнению многих — использование логарифмов и дробных степеней вполне приемлемо.
quantum
Ну сейчас не оптимизирует
q() — 625 газа
q2() — 141 газ
Про читаемость согласен