Smart Contract Authorization
NOOSChain smart-contract authorization is deliberately layered. A signed transaction proves who asked for a method to run. The contract instance defines which contract principal is executing. The manifest and bucket access rules then decide whether the requested operation is allowed.
This prevents a contract from becoming an accidental permission escalator.
Principals In A Contract Call
Every direct transaction CALL_CONTRACT has:
- a caller, resolved from the signed transaction;
- a contract actor, resolved from the deployed contract instance.
The contract actor is a first-class principal:
principalType: "contract"
principalId: <contract id>Bucket access rules can grant permissions to that contract principal in the same way they grant permissions to users and organizations.
Nested contract calls, declared in Manifest Calls, add:
- the root caller, carried from the original signed transaction;
- the immediate caller contract;
- the target contract.
The target method checks the immediate caller contract against its invoke.allow policy.
Invocation Policy
Each manifest method, described in Manifest Methods, has:
{
"invoke": {
"allow": [
{ "principalType": "organization", "principalId": "org-noos" }
]
}
}Supported invoke principals are:
| Principal | Meaning | Typical use |
|---|---|---|
anyUser | Any authenticated user caller. | Public read-style or low-risk methods. |
anyContract | Any deployed contract caller. | Reviewed C2C extension points where all contracts may call. |
any | Any authenticated user or any deployed contract caller. | Intentionally public methods shared by users and contracts. |
user | One specific user id. | Personal/admin workflows. |
organization | Members or actors resolved to an organization. | Team-scoped workflows. |
contract | One specific calling contract id. | Contract-to-contract callbacks. |
system | System-level calls. | Reserved platform workflows. |
Use the narrowest policy that matches the method. Prefer explicit users, organizations, or contract callers for writes.
any is deliberately broad. Packaging and validation tooling warn when a method uses { "principalType": "any" } because it allows both human users and contracts. Use it only when the method is intentionally public to both caller classes. Prefer anyUser for user-facing public methods and anyContract for contract-only extension points.
Bucket Authorization
Bucket-sensitive host operations require three layers to line up:
method manifest declares the bucket and permission
AND contract principal has the bucket permission
AND caller has the bucket permissionThe manifest declaration states what the method may attempt. The contract principal rule states what the deployed contract is allowed to access. The caller permission check prevents a caller from using a contract to reach a bucket the caller could not access directly.
For example, a method that writes report metadata might declare:
{
"bucketId": "bucket-reports",
"access": ["bucket:write"],
"availability": "metadata"
}The deployed contract then needs an access rule:
{
"bucketId": "bucket-reports",
"principalType": "contract",
"principalId": "contract-report-store",
"permissions": ["bucket:write"]
}And the caller needs the required bucket permission through their own user or organization access rule.
Bucket Permissions
| Permission | Contract meaning |
|---|---|
bucket:read_metadata | Method may read bucket consensus metadata. |
bucket:read_encrypted | Method may read encrypted-record metadata and require local encrypted payload availability where declared. It does not reveal plaintext. |
bucket:write | Method may add encrypted-record metadata through the host layer. |
bucket:admin | Administrative bucket capability; use sparingly for contract principals. |
Host APIs never return plaintext or raw encrypted payload bytes to contract code. Encrypted payload availability is still controlled by bucket policy, local node authorization, and backfill.
Contract-To-Contract Authorization
Nested calls must pass checks on both sides.
The caller contract declares the outbound edge:
{
"calls": [
{
"contractId": "contract-report-target",
"methods": ["summarize"],
"requiredBuckets": []
}
]
}The target method allows the caller contract:
{
"invoke": {
"allow": [
{ "principalType": "contract", "principalId": "contract-report-caller" }
]
}
}The runtime also enforces nested-call limits, fuel sharing, active/deactivated status, target manifest validity, and target readiness.
Readiness And Authorization
Readiness can fail before a call because the node can prove the deployed contract cannot satisfy its manifest locally. Common authorization-related readiness failures are:
- a manifest references a missing bucket;
- the contract principal lacks a declared bucket permission;
- local encrypted payload availability is required but missing;
- the manifest is invalid.
Check readiness before routing traffic:
npm run noos -- contracts readiness <CONTRACT_ID> --refresh --jsonCommon Failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Caller cannot invoke method. | Caller is not included in invoke.allow. | Narrowly update the manifest and publish a new version, or use an allowed caller. |
| Bucket host call fails. | Manifest lacks the bucket permission. | Add the bucket requirement to the method manifest and republish. |
| Contract is not ready. | Contract principal lacks bucket permission. | Submit transaction ADD_BUCKET_ACCESS_RULE for principalType: "contract". |
| User can invoke but bucket write fails. | Caller lacks bucket permission. | Grant the caller or caller organization the required bucket permission. |
| Nested call fails. | Caller manifest lacks calls, or target method does not allow the caller contract. | Update caller and/or target manifest and publish compatible versions. |
Design Rules
- Keep write methods away from
anyUserunless the bucket and business rules are intentionally public. - Treat
anyas broader thananyUser; it also allows contract callers and should trigger explicit review. - Grant contract principals only the bucket permissions each method needs.
- Declare C2C edges explicitly and avoid dependency cycles.
- Treat bucket
admingrants to contracts as production-risk exceptions. - Review authorization together with the manifest hash, package provenance, and registry approval.