Skip to content

Latest commit

 

History

History
326 lines (244 loc) · 26.8 KB

script.rst

File metadata and controls

326 lines (244 loc) · 26.8 KB

Язык написания контрактов

Контракт - это базовая конструкция для реализации алгоритмов eGaaS. В виде контрактов оформляются законченные фрагменты кода, обеспечивающие принятие входных данных от пользователя или другого контракта, анализ их корректности и выполнение необходимых транзакций. Язык написания контрактов - это скриптовый язык с быстрой компиляцией в байт-код. Он поддерживает переменные с основными типами значений, содержит функции, стандартный набор операторов и конструкций, систему обработки ошибок.

Контракты компилируются в байт-код и доступны для всех пользователей. При вызове контракта, происходит создание изолированного стэка с входящими данными и набором переменных, с которым и работает виртуальная машина выполняя байт-код. Таким образом, на одном и том же байт-коде может одновременно выполнятся множество процессов, которые не будут влиять друг на друга.

Переменные языка объявляются с указанием типа значения. В очевидных случаях применяется автоматическое преобразование типов. Используются следующие типы значений:

  • bool - булевый, принимает значения true или false;
  • bytes - последовательность байтов;
  • int - 64-разрядное целое число;
  • address - 64-разрядное беззнаковое целое число;
  • array - массив значений с произвольными типами;
  • map - ассоциативный массив значений с произвольными типами со строковыми ключами;
  • money - целое число типа big integer; значения хранятся в базе данных без десятичных точек, которые вставляется при выводе в интерфейсе в соответствии с настройками валюты;
  • float - 64-разрядное число с плавающей точкой;
  • string - строка; указываются в двойных или обратных кавычках - "This is a line" или This is a line.

Все идентификаторы - имена переменных, функций, контрактов и пр. - регистрозависимы (MyFunc и myFunc - это разные имена).

Переменные объявляются с помощью ключевого слова var, после которого указывается имя или имена переменных и их тип. Переменные определяются и действуют внутри фигурных скобок. При описании переменных им автоматически присваивается значение по умолчанию: для типа bool это false, для всех числовых типов - нулевые значения, для строк - пустая строка. Примеры объявления переменных:

func myfunc( val int) int {
    var mystr1 mystr2 string, mypar int
    var checked bool
    ...
    if checked {
         var temp int
         ...
    }
}

Язык поддерживает два типа массивов:

  • array - простой массив с числовым индексом, начинающимся с 0;
  • map - ассоциативный массив со строковыми ключами.

Присваивание и получение элементов осуществляется указанием индекса в квадратных скобках.

var myarr array
var mymap map
var s string

myarr[0] = 100
myarr[1] = "This is a line"
mymap["value"] = 777
mymap["param"] = "Parameter"

s = Sprintf("%v, %v, %v", myarr[0] + mymap["value"], myarr[1], mymap["param"])
// s = 877, This is a line, Parameter

Язык описания контрактов содержит стандартные условную конструкцию if и конструкцию цикла while, которые используются внутри функций, и контрактов. Эти конструкции могут вкладывать друг в друга.

После ключевого слова должно идти условное выражение. Если условное выражение возвращает число, то оно считается ложь при значении 0. Например, val == 0 эквивалентно !val, а val != 0 тоже самое, что просто val. Конструкция if может иметь блок else, который выполняется если условное выражение if ложно. В условном выражении можно использовать операции сравнения: <, >, >=, <=, ==, !=, а также || (ИЛИ) и && (И).

if val > 10 || id != $citizen {
  ...
} else {
  ...
}

Конструкция while предназначена для реализации циклов. Блок while выполняется до тех пор, пока его условие истинно. Для прекращения цикла внутри блока используется оператор break. Для исполнения блока цикла сначала используется оператор continue.

while true {
    if i > 100 {
       break
    }
    ...
    if i == 50 {
       continue
    }
    ...
}

Кроме условных выражений, язык поддерживает стандартные арифметические действия: +,-,*,/

Функция определяется с помощью ключевого слова func, после которого указывается имя функции, в круглых скобках через запятую передаваемые параметры с указанием типа, после закрывающей скобки - тип возвращаемого значения. Тело функции заключается в фигурные скобки. Если функция не имеет параметров, то круглые скобки можно опустить. Для возврата значения из функции используется ключевое слово return.

func myfunc(left int, right int) int {
    return left*right + left - right
}
func test int {
    return myfunc(10, 30) + myfunc(20, 50)
}
func ooops {
    error "Ooops..."
}

Ошибки при выполнении любой функции обрабатываются автоматически вызывая остановку выполнения контракта и вывод соответствующего сообщения.

Контракт - это базовая конструкция языка, с помощью которой реализуется выполнение единичного действия, инициированного в интерфейсе пользователем или другим контрактом. Весь программный код приложений оформляется в виде системы контрактов, взаимодействующих через базу данных или путем вызова друг друга в теле контракта.

Контракт определяется ключевым словом contract, после которого указывается имя контракта. Тело контракта заключается в фигурные скобки. Контракт состоит из трех секций:

  1. data используется для описания входящих данных (имена переменных и их типы);
  2. conditions реализует проверку входных данных на корректность;
  3. action содержит описание действия контракта.

Структура контракта:

contract MyContract {
    data {
        FromId address
        ToId   address
        Amount money
    }
    func conditions {
        ...
    }
    func action {
    }
}

Входные данные контракта, а так же параметры формы для приема этих данных описываются в секции data. Данные перечисляются построчно: сначала указывается имя переменной (передаются только переменные, а не массивы), затем тип и опционально через пробел в двойных кавычках параметры для построения формы интерфейса:

  • hidden - скрытый элемент формы;
  • optional - элемент формы без обязательного заполнения;
  • date - поле выбора даты и времени;
  • polymap - карта с выбором координат и областей;
  • map - карта с возможностью отметить место;
  • image - загрузка изображений;
  • text - ввод текста или HTML-кода в поле textarea;
  • address - поле для ввода адреса кошелька;
  • signature:contractname - строка для вызова контракта contractname, который требует подписи (подробно рассматривается в специально разделе описания).
contract my {
  data {
      Name string
      RequestId address
      Photo bytes "image optional"
      Amount money
  }
  ...
}

Входные данные контракта, описанные в секции data, передаются в другие секции через переменные с указанными именами с символом $ перед ними. Возможно определить и дополнительные переменные со знаком $, которые будут глобальными в рамках выполнения контракта, включая вложенные контракты.

В контракте доступны и предопределенные переменные, содержащие данные о транзакции, из которой был вызван данный контракт.

  • $time - время транзакции int.
  • $state - идентификатор государства int.
  • $block - номер блока, в который запечатана транзакция int.
  • $citizen - адрес гражданина, подписавшего транзакцию int.
  • $wallet - адрес кошелька подписавшего транзакцию, если контракт вне государства с state == 0.
  • $wallet_block - адрес ноды, сформировавшей блок, в который входит транзакция.
  • $block_time - время формирования блока, который содержит транзакцию с текущим контрактом int.
contract my {
  data {
      Name string
      Amount money
  }
  func conditions {
      if $Amount <= 0 {
         error "Amount cannot be 0"
      }
      $ownerId = 1232
  }
  func action {
      DBUpdate(Table("mytable"), $ownerId, "name,amount", $Name, $Amount - 10 )
      DBUpdate(Table("mytable2"), $citizen, "amount", 10 )
  }
}

В секции conditions реализуется проверка корректности полученных данных. Для оповещения о наличии ошибок используются команды: error, warning, info. По сути, они все генерируют ошибку, останавливающую работу контракта, но выводят в интерфейсе различные сообщения: критическая ошибка, предупреждение, и информативная ошибка. Например,

if fuel == 0 {
      error "fuel cannot be zero!"
}
if money < limit {
      warning Sprintf("You don't have enough money: %v < %v", money, limit)
}
if idexist > 0 {
      info "You have been already registered"
}

В секциях conditions и action контракта может быть вызван другой контракт. Для этого указывается его имя и в круглых скобках описываются необходимые параметры: в кавычках через запятую перечисляются имена передаваемых данных (из секции data вызываемого контракта), далее через запятую список переменных, содержащих передаваемые значения. Например,

MoneyTransfer("SenderAccountId,RecipientAccountId,Amount",sender_id,recipient_id,$Price)

Вложенный контракт может возвращать полученное в нем значение через объявленные в нем глобальные переменные (имя со знаком $ впереди). Вызов вложенного контракта возможен и через функцию CallContract(), для которой имя контракта передается через строковую переменную.

Поскольку язык написания контрактов позволяет выполнять вложенные контракты, то существует возможность выполнения такого вложенного контракта без ведома пользователя запустившего внешний контракт, что может привести к подписи пользователем несанкционированных им транзакций, скажем перевода денег со своего счета.

К примеру, пусть имеется контракт перевода денег MoneyTransfer:

contract MoneyTransfer {
    data {
      Recipient int
      Amount    money
    }
    ...
}

Если в некотором контракте, запущенном пользователем, будет вписана строка MoneyTransfer("Recipient,Amount", 12345, 100), то будет осуществлен перевод 100 монет на кошелек 12345. При этом пользователь, подписывающий внешний контракт, останется не в курсе осуществленной транзакции. Исключить такую ситуацию возможно, если контракт MoneyTransfer будет требовать получения дополнительной подписи пользователя при вызове его из других контрактов. Для этого необходимо:

  1. Добавить в секцию data контракта MoneyTransfer поле с именем Signature с параметрами optional и hidden, которые позволяют не требовать дополнительной подписи при прямом вызове контракта, поскольку в поле Signature уже будет подпись.
contract MoneyTransfer {
    data {
      Recipient int
      Amount    money
      Signature string "optional hidden"
    }
    ...
}
  1. Добавить в таблицу Signatures (на странице Signatures программного клиента eGaaS) запись содержащую:
  • имя контракта MoneyTransfer,
  • имена полей, значения которых будут показываться пользователю, и их текстовое описание,
  • текст, который будет выводиться при подтверждении.

В текущем примере достаточно указать два поля Receipient и Amount:

  • Title: Are you agree to send money this recipient?
  • Parameter: Receipient Text: Wallet ID
  • Parameter: Amount Text: Amount (qEGS)

Теперь если вставить вызов контракта MoneyTransfer("Recipient, Amount", 12345, 100), то будет получена системная ошибка "Signature is not defined". Если же контракт будет вызван следующим образом MoneyTransfer("Recipient, Amount, Signature", 12345, 100, "xxx...xxxxx"), то возникнет ошибка при проверке подписи. При вызове контракта проверяется подпись следующих данных: ""время оригинальной транзакции, id пользователя, значение полей указанных в таблице signatures"", и подделать эту подпись невозможно.

Для того, чтобы пользователь при вызове контракта MoneyTransfer увидел подтверждение на перевод денег, во внешний контракт необходимо добавить поле с произвольным названием и типом string и дополнительным параметром signature:contractname. При вызове вложенного контракта MoneyTransfer необходимо просто передать этот параметр. Также следует иметь в виду, что параметры для вызова защищенного контракта должны также быть описаны в секции data внешнего контракта (они могут быть скрытыми, но они все равно будут отображаться при подтверждении). Например,

contract MyTest {
  data {
      Recipient int "hidden"
      Amount  money
      Signature string "signature:MoneyTransfer"
  }
  func action {
      MoneyTransfer("Recipient,Amount,Signature",$Recipient,$Amount,$Signature)
  }
}

При отправке контракта MyTest, у пользователя будет запрошено дополнительное подтверждение для перевода суммы на указанный кошелек. Если во вложенном контракте будут указаны другие значения, например MoneyTransfer("Recipient,Amount,Signature",$Recipient, $Amount+10, $Signature), то будет получена ошибку, что подпись неверна.

eGaaS обладает многоуровневой системой управления правами на создание и редактирование таблиц базы данных, контрактов, страниц и меню интерфейса, параметров настроечной таблицы государства. Права указываются при создании и изменении перечисленных элементов в полях "Permissions" в соответствующих разделах настройки государства (смарт-контракты, таблицы, интерфейс). Права записываются в виде логических выражений и предоставляются если на момент доступа выражение имеет значение true. Если поле "Permissions" остается пустым, то оно автоматом приобретает значение false, и доступ к выполнению соответствующих действий полностью закрывается.

Фиксируются права на следующие действия:

  1. Table column permission - право на изменение значения в колонке таблицы;
  2. Table Insert permission - право на запись в таблицу новой строки;
  3. Table New Column permission - право на добавление новой колонки;
  4. Conditions for changing of Table permissions - право на изменение прав, перечисленных в п.п. 1-3;
  5. Conditions for change cmart contract - право на изменение контракта;
  6. Conditions for change page - право на изменение страницы интерфейса;
  7. Conditions for change menu - право на изменение меню;
  8. Conditions for change of State parameters - права на изменение определенного параметра настроечной таблицы государства.

Простейшим способом предоставления прав является прописывание в поле "Conditions" логического выражения $citizen == 2263109859890200332 с указанием идентификационного номера конкретного пользователя. Универсальным и рекомендуемым методом определения прав является использование функции ContractAccess("NameContract"), которой в качесвте параметров передается список контрактов, имеющих право реализовывать соответствующее действие. К примеру, в таблице счетов после прописывания в поле "Conditions" колонки amount функции ContractAccess("MoneyTransfer"), изменение значения amount будет доступно только смарт-контракту MoneyTransfer (все контракты, предусматривающие перевод денег со счета на счет, должны делать это только путем вызова контракта MoneyTransfer). Условия получения доступа к самим контрактам контролируются в секции conditions и могут быть достаточно сложными, включающими множество других контрактов и смарт-законов.

Для разрешения конфликтных или опасных для деятельности системы ситуаций в таблице State parameters введены специальные параметры (state_changing_smart_contracts, state_changing_tables, state_changing_pages), в которых прописываются условия получения прав доступа к любым смарт-контрактам, таблицам или страницам. Эти права устанавливаются специальными смарт-законами, к примеру, предусматривающими наличие судебного решения или нескольких подписей ответственных лиц.

Система контроля доступа к ресурсам благодаря использованию контрактов для фиксации прав получается гибконастраиваемой и, что самое главное, позволяет автоматически отслеживать передачу полномочий от персоны к персоне, скажем, при смене занимаемых должностей.