Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/marketplace defender autotasks #1250

Merged
merged 5 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion defender/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"dependencies": {
"@graphql-mesh/cache-localforage": "^0.95.7",
"@hypercerts-org/contracts": "0.8.11",
"@hypercerts-org/contracts": "1.0.0",
"@openzeppelin/defender-autotask-client": "1.50.0",
"@openzeppelin/defender-autotask-utils": "1.50.0",
"@openzeppelin/defender-base-client": "1.49.0",
Expand Down
168 changes: 168 additions & 0 deletions defender/src/auto-tasks/execute-taker-bid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {
AutotaskEvent,
BlockTriggerEvent,
} from "@openzeppelin/defender-autotask-utils";
import { getNetworkConfigFromName } from "../networks";
import { createClient } from "@supabase/supabase-js";
import fetch from "node-fetch";
import { BigNumber, ethers } from "ethers";
import { MissingDataError, NotImplementedError } from "../errors";
import {
HypercertExchangeAbi,
HypercertMinterAbi,
} from "@hypercerts-org/contracts";

export async function handler(event: AutotaskEvent) {
console.log(
"Event: ",
JSON.stringify(
{ ...event, secrets: "HIDDEN", credentials: "HIDDEN" },
null,
2,
),
);
const network = getNetworkConfigFromName(event.autotaskName);
const { SUPABASE_URL, SUPABASE_SECRET_API_KEY } = event.secrets;
const ALCHEMY_KEY = event.secrets[network.alchemyKeyEnvName];

const client = createClient(SUPABASE_URL, SUPABASE_SECRET_API_KEY, {
global: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fetch: (...args) => fetch(...args),
},
});

let provider;

if (ALCHEMY_KEY) {
provider = new ethers.providers.AlchemyProvider(
network.networkKey,
ALCHEMY_KEY,
);
} else if (network.rpc) {
provider = new ethers.providers.JsonRpcProvider(network.rpc);
} else {
throw new Error("No provider available");
}

// Check data availability
const body = event.request.body;
if (!("type" in body) || body.type !== "BLOCK") {
throw new NotImplementedError("Event body is not a BlockTriggerEvent");
}
const blockTriggerEvent = body as BlockTriggerEvent;
const contractAddress = blockTriggerEvent.matchedAddresses[0];
const fromAddress = blockTriggerEvent.transaction.from;
const txnLogs = blockTriggerEvent.transaction.logs;
const tx = await provider.getTransaction(blockTriggerEvent.hash);

if (!contractAddress) {
throw new MissingDataError(`body.matchedAddresses is missing`);
} else if (!fromAddress) {
throw new MissingDataError(`body.transaction.from is missing`);
} else if (!txnLogs) {
throw new MissingDataError(`body.transaction.logs is missing`);
} else if (!tx) {
throw new MissingDataError(`tx is missing`);
}

console.log("Contract address", contractAddress);
console.log("From address", fromAddress);

// TODO: Update contracts so we can use ABI from the @hypercerts-org/contracts package
const hypercertsMinterContractInterface = new ethers.utils.Interface(
HypercertMinterAbi,
);

// Parse TransferSingle events
const parsedLogs = txnLogs.map((l) => {
//Ignore unknown events
try {
return hypercertsMinterContractInterface.parseLog(l);
} catch (e) {
console.log("Failed to parse log", l);
return null;
}
});
console.log("Parsed logs: ", JSON.stringify(parsedLogs, null, 2));
const transferSingleEvents = parsedLogs.filter(
(e) => e !== null && e.name === "TransferSingle",
);

console.log(
"TransferSingle Events: ",
JSON.stringify(transferSingleEvents, null, 2),
);

if (transferSingleEvents.length !== 1) {
throw new MissingDataError(
`Unexpected saw ${transferSingleEvents.length} TransferSingle events`,
);
}

// Get claimID
const signerAddress = transferSingleEvents[0].args["from"] as string;
const itemId = BigNumber.from(transferSingleEvents[0].args["id"]).toString();

const hypercertExchangeContractInterface = new ethers.utils.Interface(
HypercertExchangeAbi,
);
// Parse TakerBid events
const takerBidEvents = txnLogs
.map((l) => {
//Ignore unknown events
try {
return hypercertExchangeContractInterface.parseLog(l);
} catch (e) {
console.log("Failed to parse log", l);
return null;
}
})
.filter((e) => e !== null && e.name === "TakerBid");

console.log("TakerBid Events: ", JSON.stringify(takerBidEvents, null, 2));

if (takerBidEvents.length !== 1) {
throw new MissingDataError(
`Unexpected saw ${takerBidEvents.length} TakerBid events`,
);
}

// Get claimID
const orderNonce = BigNumber.from(
takerBidEvents[0].args["nonceInvalidationParameters"][1],
).toString();
console.log(
"Signer Address: ",
signerAddress,
"Order nonce: ",
orderNonce,
"Fraction ID: ",
itemId,
"Chain ID: ",
network.chainId,
);

// Remove from DB
if (await tx.wait(5).then((receipt) => receipt.status === 1)) {
const deleteResult = await client
.from("marketplace-orders")
.delete()
.eq("signer", signerAddress)
.eq("chainId", network.chainId)
.eq("orderNonce", orderNonce)
.containedBy("itemIds", [itemId])
.select()
.throwOnError();
console.log("Deleted", deleteResult);

if (!deleteResult) {
throw new Error(
`Could not remove from database. Delete result: ${JSON.stringify(
deleteResult,
)}`,
);
}
}
}
11 changes: 7 additions & 4 deletions defender/src/create-sentinel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { abi } from "./HypercertMinterABI.js";
import config from "./config.js";
import { NetworkConfig } from "./networks";
import { SentinelClient } from "@openzeppelin/defender-sentinel-client";
Expand All @@ -13,12 +12,16 @@ export const createSentinel = async ({
autotaskID,
functionConditions = [],
eventConditions = [],
contractAddress,
abi,
}: {
name: string;
network: NetworkConfig;
autotaskID: string;
eventConditions?: EventCondition[];
functionConditions?: FunctionCondition[];
contractAddress: string;
abi: any;
}) => {
const client = new SentinelClient(config.credentials);
await client
Expand All @@ -27,8 +30,8 @@ export const createSentinel = async ({
network: network.networkKey,
confirmLevel: 1, // if not set, we pick the blockwatcher for the chosen network with the lowest offset
name,
addresses: [network.contractAddress],
abi: abi,
addresses: [contractAddress],
abi,
paused: false,
eventConditions,
functionConditions,
Expand All @@ -41,7 +44,7 @@ export const createSentinel = async ({
`Created sentinel`,
res.name,
"- monitoring address",
network.contractAddress,
contractAddress,
"- linked to autotask",
autotaskID,
);
Expand Down
17 changes: 11 additions & 6 deletions defender/src/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,28 @@ export const NETWORKS: SupportedNetworks = {
* We'll then subsequently use `getNetworkConfigFromName`
* to extract the network name from within the Autotask
* @param network
* @param contract
* @param name - name pre-encoding
* @returns
*/
export const encodeName = (network: NetworkConfig, name: string) =>
`[${network.networkKey}] ${name}`;
export const encodeName = (
network: NetworkConfig,
contract: "minter" | "exchange",
name: string,
) => `[${network.networkKey}][${contract}] ${name}`;

export const decodeName = (
encodedName: string,
): { networkKey: string; name: string } => {
const regex = /^\[(.+)\]\s(.+)$/;
): { networkKey: string; contract: string; name: string } => {
const regex = /^\[(.+)\]\[(.+)\]\s(.+)$/;
const match = encodedName.match(regex);
if (!match) {
throw new Error(`Invalid encoded name: ${encodedName}`);
}
const networkKey = match[1];
const name = match[2];
return { networkKey, name };
const contract = match[2];
const name = match[3];
return { networkKey, contract, name };
};

/**
Expand Down
65 changes: 59 additions & 6 deletions defender/src/rollout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,43 @@ import { createTask } from "./create-autotask";
import { createSentinel } from "./create-sentinel";
import { ApiError } from "./errors";
import { NetworkConfig, encodeName } from "./networks";
import {
HypercertExchangeAbi,
deployments,
asDeployedChain,
HypercertMinterAbi,
} from "@hypercerts-org/contracts";

export const rollOut = async (networks: NetworkConfig[]) => {
return await Promise.all(
networks.map(async (network) => {
console.log(
"Contract address",
network.chainId.toString(),
asDeployedChain(network.chainId.toString()),
deployments[asDeployedChain(network.chainId.toString())],
deployments[asDeployedChain(network.chainId.toString())]
.HypercertExchange,
);
// On allowlist created
const autoTaskOnAllowlistCreated = await createTask(
encodeName(network, "on-allowlist-created"),
encodeName(network, "minter", "on-allowlist-created"),
"on-allowlist-created",
);
if (!autoTaskOnAllowlistCreated) {
throw new ApiError(
encodeName(
network,
"minter",
"Could not create autoTask for on-allowlist-created",
),
);
}
await createSentinel({
name: encodeName(network, "AllowlistCreated"),
name: encodeName(network, "minter", "AllowlistCreated"),
network: network,
contractAddress: network.contractAddress,
abi: HypercertMinterAbi,
eventConditions: [
{ eventSignature: "AllowlistCreated(uint256,bytes32)" },
],
Expand All @@ -30,20 +47,23 @@ export const rollOut = async (networks: NetworkConfig[]) => {

// On batch minted
const autoTaskOnBatchMintClaimsFromAllowlists = await createTask(
encodeName(network, "batch-mint-claims-from-allowlists"),
encodeName(network, "minter", "batch-mint-claims-from-allowlists"),
"batch-mint-claims-from-allowlists",
);
if (!autoTaskOnBatchMintClaimsFromAllowlists) {
throw new ApiError(
encodeName(
network,
"minter",
"Could not create autoTask for batch-mint-claims-from-allowlists",
),
);
}
await createSentinel({
name: encodeName(network, "batchMintClaimsFromAllowlists"),
name: encodeName(network, "minter", "batchMintClaimsFromAllowlists"),
network: network,
contractAddress: network.contractAddress,
abi: HypercertMinterAbi,
autotaskID: autoTaskOnBatchMintClaimsFromAllowlists.autotaskId,
functionConditions: [
{
Expand All @@ -55,20 +75,23 @@ export const rollOut = async (networks: NetworkConfig[]) => {

// On single minted from allowlist
const autoTaskOnMintClaimFromAllowlist = await createTask(
encodeName(network, "mint-claim-from-allowlist"),
encodeName(network, "minter", "mint-claim-from-allowlist"),
"mint-claim-from-allowlist",
);
if (!autoTaskOnMintClaimFromAllowlist) {
throw new ApiError(
encodeName(
network,
"minter",
"Could not create autoTask for mint-claim-from-allowlist",
),
);
}
await createSentinel({
name: encodeName(network, "mintClaimFromAllowlist"),
name: encodeName(network, "minter", "mintClaimFromAllowlist"),
network: network,
contractAddress: network.contractAddress,
abi: HypercertMinterAbi,
autotaskID: autoTaskOnMintClaimFromAllowlist.autotaskId,
functionConditions: [
{
Expand All @@ -77,6 +100,36 @@ export const rollOut = async (networks: NetworkConfig[]) => {
},
],
});

// On execute taker bid
const autoTaskExecuteTakerBid = await createTask(
encodeName(network, "exchange", "execute-taker-bid"),
"execute-taker-bid",
);
if (!autoTaskExecuteTakerBid) {
throw new ApiError(
encodeName(
network,
"exchange",
"Could not create autoTask for execute-taker-bid",
),
);
}
await createSentinel({
name: encodeName(network, "exchange", "executeTakerBid"),
network: network,
autotaskID: autoTaskExecuteTakerBid.autotaskId,
contractAddress:
deployments[asDeployedChain(network.chainId.toString())]
.HypercertExchange,
abi: HypercertExchangeAbi,
functionConditions: [
{
functionSignature:
"executeTakerBid((address,bytes),(uint8,uint256,uint256,uint256,uint256,uint8,address,address,address,uint256,uint256,uint256,uint256[],uint256[],bytes),bytes,(bytes32,(bytes32,uint8)[]))",
},
],
});
}),
);
};
Loading
Loading