# Межконтрактные вызовы DChain поддерживает вызовы одного контракта из другого через host-функцию `call_contract`. Все вызовы в цепочке исполняются в рамках одной транзакции и одного `badger.Txn`. ## Синтаксис (TinyGo SDK) ```go import dc "go-blockchain/contracts/sdk" // Вызвать метод с аргументами ok := dc.CallContract(contractID, "method_name", `["arg1","arg2"]`) // Без аргументов ok := dc.CallContract(contractID, "get_price", "[]") // С числовым аргументом ok := dc.CallContract(contractID, "increment", `[42]`) ``` `contractID` — 16-символьный hex-ID контракта (как в Explorer). ## Как это работает ``` tx CALL_CONTRACT → Contract A │ │ call_contract(B, "method", args) ▼ Contract B │ │ call_contract(C, "method", args) ▼ Contract C │ └── [возврат gasUsed] │ [gasUsed заряжается на B] [возврат gasUsed] │ [gasUsed заряжается на A] ``` ### Атомарность Все изменения состояния в цепочке вызовов используют **одну** `badger.Txn`. Если родительский вызов завершился ошибкой, изменения всех подвызовов откатываются вместе с ним. Если подвызов вернул 1 (ошибку), родитель должен сам решить: паниковать или продолжать. ### Gas Gas-бюджет подвызова = `gc.Remaining()` родителя (оставшийся gas на момент вызова). После возврата, `gasUsed` подвызова списывается с родительского счётчика. Таким образом, весь gas всей цепочки вычитается из единственного `--gas` лимита транзакции. ### Caller В подвызове `dc.Caller()` возвращает contractID **вызывающего контракта** (не исходного пользователя). ``` user → Contract A → Contract B dc.Caller() == Contract A's ID ``` ### Глубина Максимальная глубина вложенности: **8**. `A → B → C → ... → H` (8 уровней) — допустимо. Попытка вызвать 9-й уровень вернёт ошибку, транзакция откатится. ## Пример: price oracle ```go // contracts/mycontract/main.go package main import dc "go-blockchain/contracts/sdk" const priceOracleID = "abcd1234abcd1234" // ID oracle-контракта //export buy func buy() { amount := dc.ArgU64(0) // Узнать цену у другого контракта if !dc.CallContract(priceOracleID, "get_price", "[]") { dc.Log("oracle unavailable") return } // После вызова цена может быть записана в shared state под известным ключом, // или oracle мог сделать transfer напрямую. dc.Log("buy executed") } func main() {} ``` ## Пример: ping (hello_go) ```go //export ping func ping() { target := dc.ArgStr(0, 64) if target == "" { dc.Log("no target") return } ok := dc.CallContract(target, "get", "[]") if ok { dc.Log("ping ok") } else { dc.Log("ping failed") } } ``` ## Ограничения | Параметр | Значение | |---------|---------| | Максимальная глубина | 8 | | Разделяемый state | одна `badger.Txn` | | Caller в подвызове | contractID родителя | | Gas бюджет подвызова | `Remaining()` родителя | | Возврат данных | только через state (нет return value) | > **Нет возврата значений.** `call_contract` возвращает только 0/1 (успех/ошибка). Для передачи данных из подвызова обратно: запишите в state под известным ключом или используйте промежуточный общий контракт. ## Безопасность - Не доверяйте `dc.Caller()` из подвызванного контракта — он будет contractID инициатора, который может быть любым задеплоенным контрактом. - Проверяйте contractID перед вызовом, если вам важно с кем вы говорите. - Помните что подвызов может изменять общий state — не вызывайте непроверенные контракты.