Голосования в MakerDAO
@bantegСегодня мы взглянем на исполнительное голосование в MakerDAO, а также проверим результаты по открытым данным из блокчейна. MakerDAO это децентрализованная организация, в которой вопросы решаются двумя типами голосований — управляющим и исполнительным. Управляющие голосования длятся какое-то время и помогают оценить поддержку какой-либо идеи. Их можно рассматривать как опрос с вариантами ответа за или против. Исполнительное же голосование куда интересней, так как оно может регулировать параметры системы, будь то комиссия стабильности, минимальный уровень обеспечения или настройка оракулов, которые предоставляют курсы валют.
Как работает голосование
Исполнительное голосование основано на идее одобрительного голосования.
- Кто угодно может выдвинуть предложение.
- Можно поддержать одно или несколько предложений.
- Предложение с наибольшей поддержкой становится действующим.
- Голосование длится вечно.
Контракты предложений
Любое предложение вносится в форме смарт-контракта. Действующее предложение получает права внести изменения в основной контракт, называемый mom. Судя по прошлым предложениям, стандартом является использовать для них ds-spell, контракт, выполняющий одно действие только один раз.
Например, только что принятое предложение о снижение комиссии стабильности с 2.5% до 0.5% годовых можно увидеть здесь. Но "увидеть" это громко сказано, из представленных данных можно понять разве что исполнено ли оно и какой контракт оно вызывает.
Удостовериться что предложение соответствует описанию можно расшифровав data
с помощью интерфейса (ABI) контракта, который оно вызывает.
Предложение вызывает функцию setFee(uint256 ray)
с параметром 1000000000158153903837946257. Не очень похоже на 0.5%, скажете вы. Осталось понять в каких единицах измерения записано это число.
В контрактах MakerDAO используются разные единицы измерения для представления чисел с разной точностью — wad, ray и rad.
Несмотря на отсутствующую документацию, по названию аргумента можно догадаться что перед нами ray
, число с 27 знаками после запятой. Соответственно, оно выражает значение 1.000000000158153903837946257.
Само значение это множитель комиссии в секунду, то есть если мы возведем его в степень, равную количеству секунд в году, должно получиться 1.005.
1.000000000158153903837946257³¹⁵³⁶⁰⁰⁰ = 1.005
Все сходится, можно голосовать. Как закончите, возвращайтесь, вас ждет еще кое-что интересное.
Контракт голосования
Теперь давайте взглянем на контракт голосования, ds-chief. Чтобы поддержать предложение, нужно заморозить токены в этом контракте в обмен на IOU токены. Фактически это стейкинг, и только залоченные токены токены считаются при подсчете голосов.
Пользователи голосуют за наборы предложений, которые хранятся в словаре slates
. Голосовать можно двумя способами:
vote(bytes32 slate)
поддержать известный набор предложений.vote(address[] yays)
поддержать список предложений, который сохраняется как набор с помощьюetch(address[] yays)
, если такой набор еще не встречался.
Если какое-то предложение набрало большинство голосов, любой желающий может вызывать функцию lift
и контракт передаст hat
контракту предложения. Фактически это волшебная шляпа, которая дает этому контракту права изменять параметры системы. Далее любой желающий может вызвать функцию cast
, "наколдовав" предложенное заклинание.
Аудит голосования
Вооружившись этими знаниями, мы можем восстановить ход голосования и даже посмотреть кто поддержал какое предложение. План такой:
- Найти все списки кандидатов из событий
Etch(bytes32 indexed slate)
. - Загрузить все предложения из словаря
slates
. - Найти все голоса и учесть для каждого пользователя только последний.
- Добавить голосам веса в соотвествии с депозитом.
Первым делом, мы находим уникальные списки кандидатов из событий Etch
. Затем пытаемся считать кандидатов, ассоциированных с каждым из списков. Для этого используем функцию slate
и идем по индексам от нуля до того момента пока не получим ошибку.
Вызовы функций голосования используют нестандартные события на основе ds-note. Эта библиотека предоставляет модификатор note
, который записывает вызовы функций как события. По событиям (или логам) работает быстрый поиск на основе фильтров Блума, который не требует сканирования всего блокчейна.
Чтобы выделить две интересующие нас функции (голосование за существующий список или голосование за адреса предложений) нам нужно сконструировать правильный фильтр. В фильтре можно указать адрес контракта, а также темы, в которых содержатся индексированные параметры. Если указать тему как список, поиск будет воспринимать это как логический оператор ИЛИ, что нам и нужно.
Первая тема это сигнатрура функции, закодированная как bytes32. В поле data события будет все необходимое, чтобы восстановить вызов.
Система голосований использует контракт-прокси в качестве горячего кошелька. Это сделано для безопасности, с такого контракта можно только голосовать или вернуть средства обратно на связанный с ним холодный кошелек.
В событие записывается адрес пользователя, но в качестве проголосовавшего будет записан адрес прокси, поэтому мы не можем полагаться на поле from
в полученных событиях. К счастью для нас, ds-note в качестве второй темы записывает адрес, который вызвал функцию (msg.sender
).
Полученный нами ранее словарь списков и соответствующих им кандидатов slates_yays
тут придется как раз кстати, в случаях когда пользователи голосовали за набор, а не за адреса. Последним шагом нужно будет добавить каждому голосу вес в соотвествии с депозитом. 1 MKR = 1 голос.
Теперь можно объединить все этапы и увидеть общую картину.
Чуть более подробный скрипт, который также восстанавливает заклинания, можно увидеть вот здесь.
Итоги
Только что закончилось первое голосование после запуска интерфейса. Последнее предложение опубликовали 17 декабря и приняли за 5 дней. Всего за него проголосовало 75 человек. Интересно, что фактически сообщество боролось всего с одним китом, который застейкал 70,000 из 70,004 MKR за предыдущее предложение.
Также стоит отметить, что и передачу шляпы и изменение параметров системы вызвали люди из сообщества, а не кто-то из фонда MakerDAO.