# schema如何约束智能合约

## Schema的文件格式

一个完整的schema由三部分组成组成：前缀列表、class、predicate

#### 前缀列表

定义前缀，是为了更简短的描述一个资源。

如果没有前缀，那么需要采用完整的URI来描述资源，如描述Person\_001实体：

```
<http://relationlabs.ai/entity/Person_001>
```

定义了前缀之后，我们描述同样的实体，只需要使用简短字符：

```
:Person_001
```

完整的前缀列表:

```
PREFIX : <http://relationlabs.ai/entity/>
PREFIX p: <http://relationlabs.ai/property/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
```

{% hint style="info" %}
需要说明的是，`rdf:` 、`xsd:` 和 `rdfs:` 是W3C提供的标准前缀，`:`和 `p:` 是 Relation Protocol中定义的前缀，分别用于表示实体和属性。
{% endhint %}

#### Class

Class是对实体的抽象，实体是class的具体实例。比如，我们定义一组Soul实体，那么可以首先定义一个Soul的class，使用rdf描述为：

```
:Soul a rdfs:Class . 
```

#### Predicate

Predicate也叫做属性，用于描述主语和宾语的关系。Predicate通常需要定义`domain`和`range`，比如：

```
p:following a rdf:Property ;
    rdfs:domain :Soul ;
    rdfs:range :Soul .
```

这段Schema定义了：

* 谓词`p:following`是一个Property。
* `p:following`是`:Soul`的一个属性。
* `p:following`对应的属性值是`:Soul`。

## Schema示例

我们以Name Service的需求为例：

* 用户可以注册一个名称，即持有该域名
* 用户对持有的域名设置解析，将域名解析到自己的地址上

想要实现上面场景，我们需要有Soul实体表示用户的地址，Namen实体表示名称。 还需要谓词来描述两者的关系，这里有两个状态需要描述，分别为hold表示地址持有域名，resolved表示地址已被该域名解析。

因此，我们在schema里需要有下面的class和predicate

* Class
  * Soul
  * Name
* Predicate
  * hold
  * resolved
  * profileURI

基于上面定义的class和predicate，我们组装成一个完整的schema：

```
PREFIX : <http://relationlabs.ai/entity/>
PREFIX p: <http://relationlabs.ai/property/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

###################
# class
###################
:Soul a rdfs:Class ;
    rdfs:label "Soul" ;
    rdfs:comment "A soul." .

:Name a rdfs:Class ;
    rdfs:label "Name" ;
    rdfs:comment "A name." .

###################
# predicate
###################
p:hold a rdf:Property ;
    rdfs:label "hold" ;
    rdfs:comment "A Soul to own a name." ;
    rdfs:domain :Soul ;
    rdfs:range :Name .

p:resolved a rdf:Property ;
    rdfs:label "resolved" ;
    rdfs:comment "The resolved name of a soul." ;
    rdfs:domain :Soul ;
    rdfs:range :Name .

p:profileURI a rdf:Property ;
    rdfs:label "profileURI" ;
    rdfs:comment "The profileURI of a soul." ;
    rdfs:domain :Soul ;
    rdfs:range xsd:string .
```

一个完整的持有域名的rdf示例：

```
:Soul_0x0000000000000000000000000000000000000011 p:hold :Name_example.
```

一个完整的被域名解析的rdf示例：

```
:Soul_0000000000000000000000000000000000000011 p:resolved :Name_resolve.
```

## Schema对合约的约束

schema定义了RDF数据格式，作为合约数据模型的词汇表，需要合约内的数据模型定义跟schema保持一致（见图 4-2），这样合约生成的RDF数据才可以被Graph Indexer正确解析。

<figure><img src="https://3668324987-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FdqevFF76s9Kq7JWAPlD3%2Fuploads%2Fgit-blob-908e3c1c7d22fd2f784e3b87cbd7761ead1b2a6d%2Fbase-schema.png?alt=media" alt=""><figcaption><p>图 4-2 Schema如何约束RDF</p></figcaption></figure>

为此，我们在合约初始化时，需要传入参数schemaURI、className以及Predicate，将schema定义的内容转化成合约可读的代码结构。其中，schemaURI可以访问得到完整的schema内容；className为schema里定义的class名称；Predicate由schema里定义的predicate名称以及range类型组成。

下面是一个初始化合约传入的参数：

```javascript
const name = 'Relation Name Service V1';
const symbol = 'SBT';
const baseURI = 'https://api.example.com/v1/';
const schemaURI = 'ar://PsqAxxDYdxfk4iYa4UpPam5vm8XaEyKco3rzYwZJ_4E';
const className = ["Domain"];
const predicates = [["hold", 3], ["resolved", 3], ["profileURI", 1]];
await semanticSBT.initialize(
        owner.address,
        name,
        symbol,
        baseURI,
        schemaURI,
        className,
        predicates);
```

可以看到， schemaURI是[Arweave](https://www.arweave.org/)上的交易哈希，可以通过Arweave的网关直接访问[schema完整内容](https://arweave.net/PsqAxxDYdxfk4iYa4UpPam5vm8XaEyKco3rzYwZJ_4E)。className以及predicates与schema里定义的class和predicate是一致的。
