1"use strict";(globalThis.webpackChunkdocs_oasis_io=globalThis.webpackChunkdocs_oasis_io||[]).push([[2458],{28453:(e,n,s)=>{s.d(n,{R:()=>r,x:()=>a});var t=s(96540);const i={},o=t.createContext(i);function r(e){const n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),t.createElement(o.Provider,{value:n},e.children)}},38502:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>r,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"adrs/0003-consensus-runtime-token-transfer","title":"ADR 0003: Consensus/Runtime Token Transfer","description":"Component","source":"@site/docs/adrs/0003-consensus-runtime-token-transfer.md","sourceDirName":"adrs","slug":"/adrs/0003-consensus-runtime-token-transfer","permalink":"/adrs/0003-consensus-runtime-token-transfer","draft":false,"unlisted":false,"editUrl":"https://github.com/oasisprotocol/adrs/edit/main/0003-consensus-runtime-token-transfer.md","tags":[],"version":"current","lastUpdatedAt":1654520756000,"frontMatter":{"sidebar_custom_props":{}},"sidebar":"adrs","previous":{"title":"ADR 0002: Go Modules Compatible Git Tags","permalink":"/adrs/0002-go-modules-compatible-git-tags"},"next":{"title":"ADR 0004: Runtime Governance","permalink":"/adrs/0004-runtime-governance"}}');var i=s(74848),o=s(28453);const r={sidebar_custom_props:{tags:void 0}},a="ADR 0003: Consensus/Runtime Token Transfer",c={},d=[{value:"Component",id:"component",level:2},{value:"Changelog",id:"changelog",level:2},{value:"Status",id:"status",level:2},{value:"Context",id:"context",level:2},{value:"Decision",id:"decision",level:2},{value:"Addresses",id:"addresses",level:3},{value:"State",id:"state",level:3},{value:"General Accounts",id:"general-accounts",level:4},{value:"Transaction Methods",id:"transaction-methods",level:3},{value:"Allow",id:"allow",level:4},{value:"Withdraw",id:"withdraw",level:4},{value:"Queries",id:"queries",level:3},{value:"Messages",id:"messages",level:3},{value:"Staking Method Call",id:"staking-method-call",level:4},{value:"Consensus Parameters",id:"consensus-parameters",level:3},{value:"Staking",id:"staking",level:4},{value:"Roothash",id:"roothash",level:4},{value:"Runtime Host Protocol",id:"runtime-host-protocol",level:3},{value:"Host to Runtime: Initialization",id:"host-to-runtime-initialization",level:4},{value:"Host to Runtime: Transaction Batch Dispatch",id:"host-to-runtime-transaction-batch-dispatch",level:4},{value:"Runtime to Host: Read-only Storage Access",id:"runtime-to-host-read-only-storage-access",level:4},{value:"Rust Runtime Support Library",id:"rust-runtime-support-library",level:3},{value:"Expected User/Consensus/Runtime Flow",id:"expected-userconsensusruntime-flow",level:3},{value:"Consequences",id:"consequences",level:2},{value:"Positive",id:"positive",level:3},{value:"Negative",id:"negative",level:3},{value:"Neutral",id:"neutral",level:3},{value:"References",id:"references",level:2}];function l(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"adr-0003-consensusruntime-token-transfer",children:"ADR 0003: Consensus/Runtime Token Transfer"})}),"\n",(0,i.jsx)(n.h2,{id:"component",children:"Component"}),"\n",(0,i.jsx)(n.p,{children:"Oasis Core"}),"\n",(0,i.jsx)(n.h2,{id:"changelog",children:"Changelog"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"2020-09-16: Beneficiary allowance, add message results"}),"\n",(0,i.jsx)(n.li,{children:"2020-09-08: Initial draft"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"status",children:"Status"}),"\n",(0,i.jsx)(n.p,{children:"Accepted"}),"\n",(0,i.jsx)(n.h2,{id:"context",children:"Context"}),"\n",(0,i.jsx)(n.p,{children:"Currently each runtime can define its own token (or none at all) and there is no\nmechanism that would support transfer of consensus layer tokens into a runtime\nand back out."}),"\n",(0,i.jsx)(n.p,{children:"Introducing such a mechanism would allow the consensus layer tokens to be used\ninside runtimes for various functions. This ADR proposes such a mechanism."}),"\n",(0,i.jsx)(n.h2,{id:"decision",children:"Decision"}),"\n",(0,i.jsx)(n.p,{children:"On a high level, this proposal adds support for consensus/runtime token\ntransfers as follows:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Each staking account can set an allowance for benef
1iciaries."})," Each staking\naccount can set an allowance, a maximum amount a beneficiary can withdraw from\nthe given account. Beneficiaries are identified by their address. This is\nsimilar to approve/transferFrom calls defined by the ",(0,i.jsx)(n.a,{href:"https://eips.ethereum.org/EIPS/eip-20",children:"ERC-20 Token Standard"}),".\nPreviously such functionality was already present but was removed in\n",(0,i.jsx)(n.a,{href:"https://github.com/oasisprotocol/oasis-core/issues/2021",children:"oasis-core#2021"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Each runtime itself has an account in the consensus layer."})," This account\ncontains the balance of tokens which are managed exclusively by the runtime\nand do not belong to any specific regular account in the consensus layer."]}),"\n",(0,i.jsx)(n.p,{children:"It is not possible to transfer directly into a runtime account and doing so\nmay result in funds to be locked without a way to reclaim them."}),"\n",(0,i.jsx)(n.p,{children:"The only way to perform any operations on runtime accounts is through the use\nof messages emitted by the runtime during each round. These messages are\nsubject to discrepancy detection and instruct the consensus layer what to do."}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Combined, the two mechanisms enable account holders to set an allowance in the\nbenefit of runtimes so that the runtimes can withdraw up to the allowed amount\nfrom the account holder's address."}),"\n",(0,i.jsx)(n.h3,{id:"addresses",children:"Addresses"}),"\n",(0,i.jsx)(n.p,{children:"This proposal introduces the following new address context for the runtime\naccounts:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"oasis-core/address: runtime\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Initial version for the address context is ",(0,i.jsx)(n.code,{children:"0"}),". To derive the address, the\nstandard address derivation scheme is used, with the runtime's 32-byte\nidentifier used as the ",(0,i.jsx)(n.code,{children:"data"})," part."]}),"\n",(0,i.jsx)(n.h3,{id:"state",children:"State"}),"\n",(0,i.jsx)(n.p,{children:"This proposal introduces/updates the following consensus state in the staking\nmodule:"}),"\n",(0,i.jsx)(n.h4,{id:"general-accounts",children:"General Accounts"}),"\n",(0,i.jsx)(n.p,{children:"The general account data structure is modified to include an additional field\nstoring the allowances as follows:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type GeneralAccount struct {\n // ... existing fields omitted ...\n\n Allowances map[Address]quantity.Quantity `json:"allowances,omitempty"`\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"transaction-methods",children:"Transaction Methods"}),"\n",(0,i.jsx)(n.p,{children:"This proposal adds the following new transaction methods in the staking module:"}),"\n",(0,i.jsx)(n.h4,{id:"allow",children:"Allow"}),"\n",(0,i.jsx)(n.p,{children:"Allow enables an account holder to set an allowance for a beneficiary."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Method name:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"staking.Allow\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Body:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type Allow struct {\n Beneficiary Address `json:"beneficiary"`\n Negative bool `json:"negative,omitempty"`\n AmountChange quantity.Quantity `json:"amount_change"`\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Fields:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"beneficiary"})," specifies the beneficiary account address."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"amount_change"})," specifies the absolute value of the amount of base units to\nchange the allowance for."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"negative"})," specifies whether the ",(0,i.jsx)(n.code,{children:"amount_change"})," should be subtracted instead\nof added."]}),"\n"]}
1),"\n",(0,i.jsx)(n.p,{children:"The transaction signer implicitly specifies the general account. Upon executing\nthe allow the following actions are performed:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["If either the ",(0,i.jsx)(n.code,{children:"disable_transfers"})," staking consensus parameter is set to ",(0,i.jsx)(n.code,{children:"true"}),"\nor the ",(0,i.jsx)(n.code,{children:"max_allowances"})," staking consensus parameter is set to zero, the method\nfails with ",(0,i.jsx)(n.code,{children:"ErrForbidden"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["It is checked whether either the transaction signer address or the\n",(0,i.jsx)(n.code,{children:"beneficiary"})," address are reserved. If any are reserved, the method fails with\n",(0,i.jsx)(n.code,{children:"ErrForbidden"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Address specified by ",(0,i.jsx)(n.code,{children:"beneficiary"})," is compared with the transaction signer\naddress. If the addresses are the same, the method fails with\n",(0,i.jsx)(n.code,{children:"ErrInvalidArgument"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"The account indicated by the signer is loaded."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["If the allow would create a new allowance and the maximum number of allowances\nfor an account has been reached, the method fails with ",(0,i.jsx)(n.code,{children:"ErrTooManyAllowances"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["The set of allowances is updated so that the allowance is updated as specified\nby ",(0,i.jsx)(n.code,{children:"amount_change"}),"/",(0,i.jsx)(n.code,{children:"negative"}),". In case the change would cause the allowance to\nbe equal to zero or negative, the allowance is removed."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"The account is saved."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["The corresponding ",(0,i.jsx)(n.code,{children:"AllowanceChangeEvent"})," is emitted with the following\nstructure:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type AllowanceChangeEvent struct {\n Owner Address `json:"owner"`\n Beneficiary Address `json:"beneficiary"`\n Allowance quantity.Quantity `json:"allowance"`\n Negative bool `json:"negative,omitempty"`\n AmountChange quantity.Quantity `json:"amount_change"`\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Where ",(0,i.jsx)(n.code,{children:"allowance"})," contains the new total allowance, the ",(0,i.jsx)(n.code,{children:"amount_change"}),"\ncontains the absolute amount the allowance has changed for and ",(0,i.jsx)(n.code,{children:"negative"}),"\nspecifies whether the allowance has been reduced rather than increased. The\nevent is emitted even if the new allowance is zero."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"withdraw",children:"Withdraw"}),"\n",(0,i.jsx)(n.p,{children:"Withdraw enables a beneficiary to withdraw from the given account."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Method name:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"staking.Withdraw\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Body:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type Withdraw struct {\n From Address `json:"from"`\n Amount quantity.Quantity `json:"amount"`\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Fields:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"from"})," specifies the account address to withdraw from."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"amount"})," specifies the amount of base units to withdraw."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"The transaction signer implicitly specifies the destination general account.\nUpon executing the withdrawal the following actions are performed:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["If either the ",(0,i.jsx)(n.code,{children:"disable_transfers"})," staking consensus parameter is set to ",(0,i.jsx)(n.code,{children:"true"}),"\nor the ",(0,i.jsx)(n.code,{children:"max_allowances"})," staking consensus parameter is set to zero, the method\nfails with ",(0,i.jsx)(n.code,{children:"ErrForbidden"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["It is checked whether either the transaction signer address or the\n",(0,i.jsx)(n.code,{children:"from"})," address are reserved. If any are reserved, the method fails with\n",(0,i.jsx)(n.code,{children:"ErrForbidden"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Address specified by ",(0,i.jsx)(n.code,{children:"from"})," is compared with the transaction signer address.\nIf the addresses are the same, the method fails with ",(0,i.jsx)(n.code,{children:"ErrInvalidArgument"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["The source account indicated by ",(0,i.jsx)(n.code,{children:"from"})," is loaded."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"The destination account indicated by the transaction signer is loaded."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"amount"})," is deducted from the corresponding allowance in the source account.\nIf this would cause the allowance to go negative, the method fails with\n",(0,i.jsx)(n.code,{children:"ErrForbidden"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"amount"})," is deducted from the source general account balance. If this would\ncause the balance to go negative, the method fails with\n",(0,i.jsx)(n.code,{children:"ErrInsufficientBalance"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"amount"})," is added to the destination general account balance."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Both source and destination accounts are saved."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["The corresponding ",(0,i.jsx)(n.code,{children:"TransferEvent"})," is emitted."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["The corresponding ",(0,i.jsx)(n.code,{children:"AllowanceChangeEvent"})," is emitted with the updated\nallowance."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"queries",children:"Queries"}),"\n",(0,i.jsxs)(n.p,{children:["This proposal adds the following new query methods in the staking module by\nupdating the ",(0,i.jsx)(n.code,{children:"staking.Backend"})," interface as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type Backend interface {\n // ... existing methods omitted ...\n\n // Allowance looks up the allowance for the given owner/beneficiary comb
1ination.\n Allowance(ctx context.Context, query *AllowanceQuery) (*quantity.Quantity, error)\n}\n\n// AllowanceQuery is an allowance query.\ntype AllowanceQuery struct {\n Height int64 `json:"height"`\n Owner Address `json:"owner"`\n Beneficiary Address `json:"beneficiary"`\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"messages",children:"Messages"}),"\n",(0,i.jsx)(n.p,{children:"Since this is the first proposal that introduces a new runtime message type that\ncan be emitted from a runtime during a round, it also defines some general\nproperties of runtime messages and the dispatch mechanism:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Each message has an associated gas cost that needs to be paid by the\nsubmitter (e.g. as part of the ",(0,i.jsx)(n.code,{children:"roothash.ExecutorCommit"})," method call). The gas\ncost is split among the committee members."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"There is a maximum number of messages that can be emitted by a runtime during\na given round. The limit is defined both globally (e.g. a roothash consensus\nparameter) and per-runtime (which needs to be equal to or lower than the\nglobal limit)."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Messages are serialized using a sum type describing all possible messages,\nwhere each message type is assigned a ",(0,i.jsx)(n.em,{children:"field name"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type Message struct {\n Message1 *Message1 `json:"message1,omitempty"`\n Message2 *Message2 `json:"message2,omitempty"`\n // ...\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["All messages are versioned by embeding the ",(0,i.jsx)(n.code,{children:"cbor.Versioned"})," structure which\nprovides a single ",(0,i.jsx)(n.code,{children:"uint16"})," field ",(0,i.jsx)(n.code,{children:"v"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"A change is made to how messages are included in commitments, to reduce the\nsize of submitted transactions."}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"ComputeResultsHeader"})," is changed so that the ",(0,i.jsx)(n.code,{children:"Messages"})," field is replaced\nwith a ",(0,i.jsx)(n.code,{children:"MessagesHash"})," field containing a hash of the CBOR-encoded messages\nemitted by the runtime."]}),"\n",(0,i.jsxs)(n.p,{children:["At the same time ",(0,i.jsx)(n.code,{children:"ComputeBody"})," is changed to include an additional field\n",(0,i.jsx)(n.code,{children:"Messages"})," as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type ComputeBody struct {\n // ... existing fields omitted ...\n Messages []*block.Message `json:"messages,omitempty"`\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"Messages"})," field must only be populated in the commitment by the\ntransaction scheduler and must match the ",(0,i.jsx)(n.code,{children:"MessagesHash"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["If any of the included messages is deemed ",(0,i.jsx)(n.em,{children:"malformed"}),", the round fails and the\nruntime state is not updated."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"In order to support messages that fail to execute, a new roothash event is\nemitted for each executed message:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type MessageEvent struct {\n Index uint32 `json:"index,omitempty"`\n Module string `json:"module,omitempty"`\n Code uint32 `json:"code,omitempty"`\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Where the ",(0,i.jsx)(n.code,{children:"index"})," specifies the index of the executed message and the ",(0,i.jsx)(n.code,{children:"module"}),"\nand ",(0,i.jsx)(n.code,{children:"code"})," specify the module and error code accoording to Oasis Core error\nencoding convention (note that the usual human readable message field is not\nincluded)."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"This proposal introduces the following runtime messages:"}),"\n",(0,i.jsx)(n.h4,{id:"staking-method-call",children:"Staking Method Call"}),"\n",(0,i.jsx)(n.p,{children:"The staking method call message enables a runtime to call one of the supported\nstaking module methods."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Field name:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"staking\n"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Body:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type StakingMessage struct {\n cbor.Versioned\n\n Transfer *staking.Transfer `json:"transfer,omitempty"`\n Withdraw *staking.Withdraw `json:"withdraw,omitempty"`\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Fields:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"v"})," must be set to ",(0,i.jsx)(n.code,{children:"0"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"transfer"})," indicates that the ",(0,i.jsx)(n.code,{children:"staking.Transfer"})," method should be executed."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"withdraw"})," indicates that the ",(0,i.jsx)(n.code,{children:"staking.Withdraw"})," method should be executed."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Exactly one of the supported method fields needs to be non-nil, otherwise the\nmessage is considered malformed."}),"\n",(0,i.jsx)(n.h3,{id:"consensus-parameters",children:"Consensus Parameters"}),"\n",(0,i.jsx)(n.h4,{id:"staking",children:"Staking"}),"\n",(0,i.jsx)(n.p,{children:"This proposal introduces the following new consensus parameters in the staking\nmodule:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"max_allowances"})," (uint32) specifies the maximum number of allowances an\naccount can store. Zero means that allowance functionality is disabled."]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"roothash",children:"Roothash"}),"\n",(0,i.jsx)(n.p,{children:"This proposal introduces the following new consensus parameters in the roothash\nmodule:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"max_runtime_messages"})," (uint32) specifies the global limit on the number of\nmessages that can be emitted in each round by the runtime. The default value\nof ",(0,i.jsx)(n.code,{children:"0"})," disables the use of runtime messages."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"runtime-host-protocol",children:"Runtime Host Protocol"}),"\n",(0,i.jsx)(n.p,{children:"This proposal modifies the runtime host protocol as follows:"}),"\n",(0,i.jsx)(n.h4,{id:"host-to-runtime-initialization",children:"Host to Runtime: Initialization"}),"\n",(0,i.jsxs)(n.p,{children:["The existing ",(0,i.jsx)(n.code,{children:"RuntimeInfoRequest"})," message body is updated to contain a field\ndenoting the consensus backend used by the host and its consensus protocol\nversion as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type RuntimeInfoRequest struct {\n ConsensusBackend str
1ing `json:"consensus_backend"`\n ConsensusProtocolVersion uint64 `json:"consensus_protocol_version"`\n\n // ... existing fields omitted ...\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:"This information can be used by the runtime to ensure that it supports the\nconsensus layer used by the host. In case the backend and/or protocol version is\nnot supported, the runtime should return an error and terminate. In case the\nruntime does not interact with the consensus layer it may ignore the consensus\nlayer information."}),"\n",(0,i.jsx)(n.h4,{id:"host-to-runtime-transaction-batch-dispatch",children:"Host to Runtime: Transaction Batch Dispatch"}),"\n",(0,i.jsxs)(n.p,{children:["The existing ",(0,i.jsx)(n.code,{children:"RuntimeExecuteTxBatchRequest"})," and ",(0,i.jsx)(n.code,{children:"RuntimeCheckTxBatchRequest"}),"\nmessage bodies are updated to include the consensus layer light block at the\nlast finalized round height (specified in ",(0,i.jsx)(n.code,{children:".Block.Header.Round"}),") and the list of\n",(0,i.jsx)(n.code,{children:"MessageEvent"}),"s emitted while processing the runtime messages emitted in the\nprevious round as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type RuntimeExecuteTxBatchRequest struct {\n // ConsensusBlock is the consensus light block at the last finalized round\n // height (e.g., corresponding to .Block.Header.Round).\n ConsensusBlock consensus.LightBlock `json:"consensus_block"`\n\n // MessageResults are the results of executing messages emitted by the\n // runtime in the previous round (sorted by .Index).\n MessageResults []roothash.MessageEvent `json:"message_results,omitempty"`\n\n // ... existing fields omitted ...\n}\n\ntype RuntimeCheckTxBatchRequest struct {\n // ConsensusBlock is the consensus light block at the last finalized round\n // height (e.g., corresponding to .Block.Header.Round).\n ConsensusBlock consensus.LightBlock `json:"consensus_block"`\n\n // ... existing fields omitted ...\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:"The information from the light block can be used to access consensus layer\nstate."}),"\n",(0,i.jsx)(n.h4,{id:"runtime-to-host-read-only-storage-access",children:"Runtime to Host: Read-only Storage Access"}),"\n",(0,i.jsxs)(n.p,{children:["The existing ",(0,i.jsx)(n.code,{children:"HostStorageSyncRequest"})," message body is updated to include an\nendpoint identifier as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-golang",children:'type HostStorageSyncRequest struct {\n // Endpoint is the storage endpoint to which this request should be routed.\n Endpoint string `json:"endpoint,omitempty"`\n\n // ... existing fields omitted ...\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The newly introduced ",(0,i.jsx)(n.code,{children:"endpoint"})," field can take the following values:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"runtime"})," (or empty string) denotes the runtime state endpoint. The empty\nvalue is allowed for backwards compatibility as this was the only endpoint\navailable before this proposal."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"consensus"})," denotes the consensus state endpoint, providing access to\nconsensus state."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"rust-runtime-support-library",children:"Rust Runtime Support Library"}),"\n",(0,i.jsxs)(n.p,{children:["The Rust runtime support library (",(0,i.jsx)(n.code,{children:"oasis-core-runtime"}),") must be updated to\nsupport the updated message structures. Additionally, there needs to be basic\nsupport for interpreting the data from the Tendermint consensus layer backend:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Decoding light blocks."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Decoding staking-related state structures."}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"The Tendermint-specific functionality should be part of a separate crate."}),"\n",(0,i.jsx)(n.h3,{id:"expected-userconsensusruntime-flow",children:"Expected User/Consensus/Runtime Flow"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Scenario:"})}),"\n",(0,i.jsx)(n.p,{children:"Account holder has 100 tokens in her account in the consensus layer staking\nledger and would like to spend 50 tokens to execute an action in runtime X."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Flow:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holder sets an allowance of 50 tokens for runtime X by submitting an\nallow transaction to the consensus layer."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holder submits a runtime transaction that performs s
1ome action costing\n50 tokens."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holder's runtime transaction is executed in runtime X round R:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Runtime X emits a message to transfer 50 tokens from the user's account to\nthe runtime's own account."}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"As an optimization runtime X can verify current consensus layer state and\nreject the transaction early to prevent paying for needless consensus layer\nmessage processing."})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Runtime X updates its state to indicate a pending transfer of 50 tokens from\nthe user. It uses the index of the emitted message to be able to match the\nmessage execution result once it arrives."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Runtime X submits commitments to the consensus layer."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"When finalizing round R for runtime X, the consensus layer transfers 50 tokens\nfrom the account holder's account to the runtime X account."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Corresponding message result event is emitted, indicating success."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"When runtime X processes round R+1, the runtime receives the set of emitted\nmessage result events."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Runtime X processes message result events, using the index field to match the\ncorresponding pending action and executes whatever action it queued."}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"In case the message result event would indicate failure, the pending action\ncan be pruned."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"consequences",children:"Consequences"}),"\n",(0,i.jsx)(n.h3,{id:"positive",children:"Positive"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Consensus layer tokens can be transferred into and out of runtimes, enabling\nmore use cases."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Any tokens must be explicitly made available to the runtime which limits the\ndamage from badly written or malicious runtimes."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holders can change the allowance at any time."}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"negative",children:"Negative"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"A badly written or malicious runtime could steal the tokens explicitly\ndeposited into the runtime. This includes any actions by the runtime owner\nwhich would modify the runtime's security parameters."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"A badly written, malicious or forever suspended runtime can lock tokens in\nthe runtime account forever. This could be mitigated via an unspecified\nconsensus layer governance mechanism."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holders may mistakenly transfer tokens directly into a runtime account\nwhich may cause such tokens to be locked forever."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Account holders may change the allowance or reduce their account balance right\nbefore the runtime round is finalized, causing the emitted messages to fail\nwhile the runtime still needs to pay for gas to execute the messages."}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"neutral",children:"Neutral"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"The runtime must handle all message results in the next round as otherwise it\ncannot easily get past messages."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"references",children:"References"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://eips.ethereum.org/EIPS/eip-20",children:"ERC-20 Token Standard"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://github.com/oasisprotocol/oasis-core/issues/2021",children:"oasis-core#2021"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.