DRAFT — черновик (статья находится в стадии доработки)
В предыдущем уроке мы рассмотрели механизм возврата средств. В этом мы мы узнаем как реализовать в смарт-контракте реферальную систему.
Реферальная система.
Основные способы привлечения новых инвесторов — это PR и реклама. Однако, в руках у разработчика есть мощный инструмент привлечения инвесторов — реферальная система. Смысл ее прост. Тому кто привлек вам инвестора начисляется процент от токенов инвестора.
Чем уникален этот инструмент?
- Он мотивирует других людей привлекать вам инвесторов. Те кто привлекли получают бесплатные токены.
- Он мотивирует других людей привлекать инвесторов с большими суммами. Те кто привлекли получают процент, а значит мотивированы привлечь большого инвестора.
Поэтому такая система позволяет увеличивает не только количество но и качество инвесторов. На практике, конечно, люди стараются привлечь всех подряд. Поэтому пункт 2 не сильно работает. Но в теории он хорош и может сработать в редких случаях.
В реферальной системе, как вы уже заметили, участвую два человека.
- Тот кто привлекает — реферер
- Тот кого привлекли — реферал
Как это реализуется на практике?
На практике не все так просто как в теории. Вся загвоздка состоит в том, чтобы простым способом передать данные реферера, дабы начислить ему бонусы. Способ должен быть именно простым. А в идеале вообще автоматическим. Потому что привлеченный инвестор не захочит долго возиться с передачей информации о том, кто его привлек. А может быть и вообще передумает инвестировать. Итак, реферер привлек реферала и просит реферала сказать кто его привлек.
Чтобы рефереру начислить бонусы инвестор указывает его реферера при покупке токенов. Единственное место где мы можем указать адрес реферера — поле дополнительных данных.
В MyEtherWallet это поле показывается при нажатии на ссылке «Дополнительные данные». И там можно ввести адрес реферера.
Итак, мы определились как инвестор будет передавать данные о привлекшем его реферере. Осталось реализовать это в контракте. Но… Мы знаем что функция fallback не принимает параметры. Вот тут то и заключается вся сложность. На самом деле, при вызове любой функции, передаются следующие данные:
- Уникальный идентификатор функции. Получается с помощью применения специальной хэш-функции к названию функции.
- Аргументы
Доступ к этим данным можно получить внутри функции обратившись к msg.data. Первые четыре байта в msg.data — уникальный идентификатор функции. Но, поскольку fallabck функция не имеет названия, то и идентификатор будет отсутствовать. А значит наши данные идут сразу в msg.data.
Откуда получать мы определились. Теперь надо понять как извлекать наш адрес. Дело в том что msg.data имеет специальный тип callvalue. Прямо его можно преобразовать только к bytes. Но bytes не преобразуется прямо к address. Зато в address преобразуется uint. А uint к address. Вообщем запутаться можно легко. Итак, цепочка преобразований у нас будет такая:
- callvalue => bytes
- bytes => uint
- uint => address
Первое преобразование:
1 |
bytes(msg.data); |
Второе преобразование. Для того чтобы преобразовать bytes в uint, нам потребуется вспомнить преобразование из шестнадцатиричной системы в десятичную. В шестнадцатиричной системе 1 байт преобразуется в число десятичной системы от 0 до 255. Но каждый байт, еще имеет свое место — порядок. Таким образом каждый байт нужно еще умножить на 256 в степени его порядка. Например, возьмем три байта 6F5423 и преобразуем.
6F | 54 | 23 |
десятичное: 111, разряд 2 | десятичное: 84, разряд 1 | десятичное: 35, разряд 0 |
111*256*256 | 84*256 | 35 |
7274496 | 21504 | 805 |
Cуммарно: 7296805 |
Думаю, идея понятна. В коде наше преобразование будет выглядеть вот так:
1 2 3 4 5 6 |
uint result; uint mul = 1; for(uint i = 20; i > 0; i--) { result += uint8(source[i-1])*mul; mul = mul*256; } |
Почему именно 20? Да потому что длина адреса эфира равна 20 байтам. Решение не самое изящное, но нам пока главное понять суть.
Третье преобразование.
1 |
address(result); |
Оформим наше преобразование bytes в адрес в виде функции:
1 2 3 4 5 6 7 8 9 |
function bytesToAddress(bytes source) internal pure returns(address) { uint result; uint mul = 1; for(uint i = 20; i > 0; i--) { result += uint8(source[i-1])*mul; mul = mul*256; } return address(result); } |
Теперь мы его можем использовать в контракте распродажи. Алгоритм работы функции покупки будет примерно такой:
- Считаем сколько токенов должны начислить инвестору.
- Проверяем был ли указан реферер.
- Начисляем рефереру процент от токенов инвестора.
Пусть начисленные инвестору токены у нас будут tokens, а процент рефереру — 2%. Тогда токены, которые мы должны начислить рефереру:
1 |
uint refererTokens = tokens.mul(2).div(100); |
Нам привычно сначала делить на 100 процентов, а потом умножать на нужное количество процентов. Но в Solidity нет дробей. Поэтому если мы сразу разделим, а потом умножим, то потеряем дробную часть. Поэтому возьмите себе за правило: в Solidity сначала умножайте, а потом делите.
Теперь давайте подумаем как проверить, был ли указан адрес реферера. Если адрес реферера был указан, то длина данных внутри msg.data равна длине адреса, т.е. равна 20 байтам. Так и будем проверять. Давайте посмотрим как будет выглядеть участок кода, который отвечает за начисление бонуса рефереру.
1 2 3 4 5 6 7 8 |
... if(msg.data.length == 20) { address referer = bytesToAddres(bytes(msg.data)); uint refererTokens = tokens.mul(2).div(100); // начисляем рефереру token.transfer(referer, refererTokens); } ... |
Такой код уже может начислять рефереру бонсуы. Но, представьте что инвестор в качестве адреса реферера укажет свой. Тогда он получит токены, которые по логике получить не должен. Поэтому нам надо вставить соответствующую проверку.
1 2 3 4 5 6 7 8 9 10 |
... if(msg.data.length == 20) { address referer = bytesToAddres(bytes(msg.data)); // проверка, чтобы инвестор не начислил бонусы сам себе require(referer != msg.sender); uint refererTokens = tokens.mul(2).div(100); // начисляем рефереру token.transfer(referer, refererTokens); } ... |
В этом уроке мы уже не будем приводить полный код всех смарт-контрактов. Читателю осталось только поместить данный код в fallback функцию контракта распродажи или в функцию, которая вызывается fallback функцией.
Как я уже говорил ранее, преобразование из bytes в address у нас не самое лучшее. Поэтому вы можете поискать решение получше и привести его в комментариях.
А для самых продвинутых разработчиков предлагается объяснить как работает вот эта функция:
1 2 3 4 5 6 |
function bytesToAddress1(bytes source) internal constant returns(address parsedReferer) { assembly { parsedReferer := mload(add(source,0x14)) } return parsedReferer; } |
Продолжение читать тут. Предыдущий урок тут.
Если у вас возникли вопросы то можете смело писать на электронную почту (раздел «контакты«). Также приветствуется критика.
Если статья показалась вам полезной и вы желаете отблагодарить автора, то это можно сделать отослав немного эфира на адрес 0xEA15Adb66DC92a4BbCcC8Bf32fd25E2e86a2A770.
Строго говоря проверка адреса реферала не нужна. Ни кто не мешает инвестору прописать туда свой второй кошелек. Можно предварительно проверять баланс второго кошелька и в условиях прописать что там должен быть не ноль, но и это обходится инвестированием допустим без реферера минимальной суммы а потом с другого кошелька указать реферером свой первый.
Верно. Описанное тут работает скорее как фильтр. Часть отсеится. Можно регистрировать и рефералов и рефереров. Тогда проблему избежим, если регать их в контракте. Но часть инвесторов отсеим. Поэтому проще ограничиться минимальным фильтром, простым в реализации.
Это можно решить только путем автоматического добавления адреса реферера. Вот только как сделать чтобы адрес реферера автоматически туда добавлялся?
Это будет хорошо работать, если инвестировать через свой личный кабинет а не напрямую через кошелек
Насколько помню, в функции token.transfer(referer, refererTokens) токены списываются с msg.sender. Если мы ее поместим в функцию createTokens(), которая вызывается fallback-ом контракта, то что будет лежать в msg.sender внутри функции token.transfer: адрес контракта (который вызвал token.transfer) или адрес того, кто перечислил эфиры на контракт (вызвал fallback)? И откуда будут списываться токены на реферальную программу? На счете контракта у нас токенов вроде нет.
может стоит использовать token.mint(referer, refererTokens)?
Тут рассматривается именно начисление токенов по реф.системе. Поэтому не было привязки к контракту. Но в общем вы правы, поскольку контекст у нас mintable токен, то логичнее от него отталкиваться. Исправлю, спасибо.
Здравствуйте! Вопрос про ICO: есть ли возможность распределения токена между определенным списком адресов? Что-то типа массовой (автоматической) рассылки. В интернете нигде информации не нашел. На Тостере ответили что такое возможно. Знакомый сказал что видел где-то что это возможно с помощи кода в контракте токена. Возможно вы поможете?
а можно сделать так чтобы не токены распределялись а эфиры? И точно также чтобы эфиры приходили владельцу контракта а не токены?
Эпоха Криптовалют и увидел большой потенциал в смарт-контрактах в перспективах,но не мог найти каких то мануалов в этой области.
Большое спасибо за отличные статьи!
Добрый день! Хорошее начало, но уже год, как нет обновлений. Будет продолжение?