以太坊智能合约的常见模式是使用注册表或工厂合约,其中一个合约创建、管理或引用任意数量的其他合约,每个合约都有自己的状态和事件。这些下级合约的地址可能事先就知道,也可能不知道,其中许多合约可能会随着时间的推移而创建或添加。这就是为什么在这种情况下,不可能定义单个数据源或固定数量的数据源,而需要一种更动态的方法:数据源模板。
智能合约
之前的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留到后面动态部署。
subgraph.yaml
添加templates配置,其配置项基本上和dataSources差不多,source中不支持address和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
schema.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
}
factory.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,创建了四个GravatarRegistry合约。其groupName分别为test1,test2,test3,test4。
实际上这种由工厂动态创建的智能合约,应该还有一个路由合约通过groupName跳转访问,这里简化示例的复杂性直接通过工厂合约返回的地址手动调用GravatarRegistry合约的函数。
分别调用了四个合约的createGravatar方法,查询验证:
示例代码如下: