Ethereum スマート コントラクトの一般的なパターンは、レジストリ コントラクトまたはファクトリー コントラクトを使用することです。この場合、1 つのコントラクトが、それぞれ独自の状態とイベントを持つ他のコントラクトを任意の数だけ作成、管理、または参照します。これらの下位契約のアドレスは、事前にわかっている場合もあれば、わかっていない場合もあり、これらの契約の多くは、時間の経過とともに作成または追加される可能性があります。そのため、この場合、単一のデータ ソースや固定数のデータ ソースを定義することはできず、より動的なアプローチ、つまりデータ ソース テンプレートが必要になります。
スマートコントラクト
以前の GravatarRegistry コントラクトは手動でデプロイされましたが、今回はこれを少し変更して、ファクトリ コントラクトを通じて GravatarRegistry コントラクトを動的に作成します。
まず工場契約:
// SPDX-License-Identifier: MIT
pragma solidity >0.5.16;
import './GravatarRegistry.sol';
import './IGravatarRegistry.sol';
contract GravatarRegistryFactory {
mapping(string => address) public getRegistry;
string[] public allRegistries;
event RegistryCreated(string gravatarGroup, address indexed registry,uint);
function allRegistriesLength() external view returns (uint) {
return allRegistries.length;
}
function createGravatarRegistry(string calldata _gravatarGroup) external returns (address registry) {
require(getRegistry[_gravatarGroup] == address(0), 'GravatarRegistry: ALREADY_EXISTS');
bytes memory bytecode = type(GravatarRegistry).creationCode;
bytes32 salt = keccak256(abi.encodePacked(_gravatarGroup));
assembly {
registry := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IGravatarRegistry(registry).initialize(_gravatarGroup);
getRegistry[_gravatarGroup] = registry;
allRegistries.push(_gravatarGroup);
emit RegistryCreated(_gravatarGroup, registry, allRegistries.length);
}
}
インターフェースを追加します。
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
interface IGravatarRegistry {
event NewGravatar(uint id, address owner, string displayName, string imageUrl);
event UpdatedGravatar(uint id, address owner, string displayName, string imageUrl);
function createGravatar(string calldata _displayName, string calldata _imageUrl) external;
function getGravatar(address owner) external view returns (string memory, string memory);
function updateGravatarName(string calldata _displayName) external;
function updateGravatarImage(string calldata _imageUrl) external;
function setMythicalGravatar() external ;
function initialize(string calldata _groupName) external ;
}
GravatarRegistry コントラクトは、元のコントラクトに groupName 変数を追加します。
// SPDX-License-Identifier: MIT
pragma solidity >0.4.0;
contract GravatarRegistry {
event NewGravatar(uint id, address owner, string displayName, string imageUrl);
event UpdatedGravatar(uint id, address owner, string displayName, string imageUrl);
struct Gravatar {
address owner;
string displayName;
string imageUrl;
}
address public factory;
string public groupName;
Gravatar[] public gravatars;
mapping (uint => address) public gravatarToOwner;
mapping (address => uint) public ownerToGravatar;
constructor() public {
factory = msg.sender;
}
function createGravatar(string calldata _displayName, string calldata _imageUrl) public {
require(ownerToGravatar[msg.sender] == 0);
gravatars.push(Gravatar(msg.sender, _displayName, _imageUrl));
uint id = gravatars.length - 1;
gravatarToOwner[id] = msg.sender;
ownerToGravatar[msg.sender] = id;
emit NewGravatar(id, msg.sender, _displayName, _imageUrl);
}
function getGravatar(address owner) public view returns (string memory, string memory) {
uint id = ownerToGravatar[owner];
return (gravatars[id].displayName, gravatars[id].imageUrl);
}
function updateGravatarName(string calldata _displayName) public {
require(ownerToGravatar[msg.sender] != 0);
require(msg.sender == gravatars[ownerToGravatar[msg.sender]].owner);
uint id = ownerToGravatar[msg.sender];
gravatars[id].displayName = _displayName;
emit UpdatedGravatar(id, msg.sender, _displayName, gravatars[id].imageUrl);
}
function updateGravatarImage(string calldata _imageUrl) public {
require(ownerToGravatar[msg.sender] != 0);
require(msg.sender == gravatars[ownerToGravatar[msg.sender]].owner);
uint id = ownerToGravatar[msg.sender];
gravatars[id].imageUrl = _imageUrl;
emit UpdatedGravatar(id, msg.sender, gravatars[id].displayName, _imageUrl);
}
// the gravatar at position 0 of gravatars[]
// is fake
// it's a mythical gravatar
// that doesn't really exist
// dani will invoke this function once when this contract is deployed
// but then no more
function setMythicalGravatar() public {
require(msg.sender == 0xBA8B604410ca76AF86BDA9B00Eb53B65AC4c41AC);
gravatars.push(Gravatar(address(0x0), " ", " "));
}
// called once by the factory at time of deployment
function initialize(string calldata _groupName) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
groupName = _groupName;
}
}
ファクトリ コントラクトをデプロイし、後で動的にデプロイするために GravatarRegistry を残しておきます。
サブグラフ.yaml
テンプレート設定を追加します。その設定項目は基本的に dataSource と同様です。アドレスと startBlock はソースではサポートされていません
specVersion: 0.0.5
schema:
file: ./schema.graphql
features:
- fullTextSearch
dataSources:
- kind: ethereum/contract
name: Factory
network: goerli
source:
address: '0x580b57db55a0636B53Cb09BbA4aE8CbB765D72bA'
abi: Factory
startBlock: 8403815
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/factory.ts
entities:
- Registry
abis:
- name: Factory
file: ./abis/Factory.json
eventHandlers:
- event: RegistryCreated(string,indexed address,uint256)
handler: handleNewRegistry
templates:
- kind: ethereum
name: GravatarRegistry
network: goerli
source:
abi: GravatarRegistry
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
- Block
abis:
- name: GravatarRegistry
file: ./abis/GravatarRegistry.json
eventHandlers:
- event: NewGravatar(string,uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(string,uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- handler: handleBlockWithCallToContract
filter:
kind: call
file: ./src/gravatar-registry.ts
スキーマ.graphql
type _Schema_
@fulltext(
name: "gravatarSearch"
language: en
algorithm: rank
include: [{ entity: "Gravatar", fields: [{ name: "displayName" }, { name: "description" }] }]
)
type Gravatar @entity(immutable: false) {
id: String!
owner: Bytes! # address
groupName: String! # string
displayName: String! # string
description: String! # string
imageUrl: String! # string
transaction: Transaction
}
type Transaction @entity(immutable: true) {
id: Bytes!
block: Block
gasPrice: BigInt! # bigInt
}
type Block @entity(immutable: true) {
id: Bytes!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactions: [Transaction!] @derivedFrom(field: "block")
}
type Registry @entity(immutable: false) {
id: String!
address: Bytes! # address
groupName: String! # string
}
ファクトリー.ts
import {
RegistryCreated as RegistryCreatedEvent,
} from "../generated/Factory/Factory"
import { Registry} from "../generated/schema"
import { GravatarRegistry } from "../generated/templates"
export function handleNewRegistry(event: RegistryCreatedEvent): void {
let registry = new Registry(event.params.param2.toString());
registry.address = event.params.registry;
registry.groupName = event.params.gravatarGroup
registry.save()
GravatarRegistry.create(event.params.registry);
}
これは主に工場契約を監視するために使用され、重要なのは、監視対象の契約データ ソースを動的に作成するコードの最後の行です。
gravatar-registry.ts
ここでも、主に groupName フィールドを追加するために、いくつかの変更を行う必要があります。
import {
CreateGravatarCall,
NewGravatar as NewGravatarEvent,
UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/templates/GravatarRegistry/GravatarRegistry"
import { Gravatar,Transaction,Block} from "../generated/schema"
import { ethereum } from '@graphprotocol/graph-ts'
export function handleNewGravatar(event: NewGravatarEvent): void {
let gravatar = new Gravatar(event.params.id.toString()+event.params.groupName);
gravatar.owner = event.params.owner
gravatar.groupName = event.params.groupName
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.description = event.params.imageUrl.replaceAll("/"," ");
gravatar.transaction = event.transaction.hash
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {
let id = event.params.id.toString();
let gravatar = Gravatar.load(id+event.params.groupName)
if (gravatar == null) {
gravatar = new Gravatar(id+event.params.groupName)
}
gravatar.groupName = event.params.groupName
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.description = event.params.imageUrl.replaceAll("/"," ");
gravatar.imageUrl = event.params.imageUrl
gravatar.transaction = event.transaction.hash
gravatar.save()
}
リリースを再コンパイルします:
graph codegen
graph build
yarn deploy
確認
最後に、ファクトリ コントラクトの createGravatarRegistry メソッドを呼び出します。ここでは、test1、test2、test3、test4 がそれぞれ渡され、4 つの GravatarRegistry コントラクトが作成されます。その groupName はそれぞれ test1、test2、test3、test4 です。
実際、ファクトリによって動的に作成されたこの種のスマート コントラクトには、groupName ジャンプを通じてアクセスされるルーティング コントラクトも必要です。ここでは、例の複雑さは簡略化されており、GravatarRegistry コントラクトの関数は、によって返されたアドレスを通じて手動で直接呼び出されます。工場契約。
4 つのコントラクトの createGravatar メソッドをそれぞれ呼び出して、クエリ検証を行います。
サンプルコードは次のとおりです。