Skip to content

Commit 0c464c7

Browse files
authored
Merge pull request #1964 from ArtBlocks/ryley/pro-2870-develop-injectblocktimestamp-hook-contract
Add new standard PostParam hook InjectBlockTimestamp
2 parents b8d04ca + e2a67f4 commit 0c464c7

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
// Created By: Art Blocks Inc.
3+
4+
pragma solidity 0.8.22;
5+
6+
import {AbstractPMPAugmentHook} from "./AbstractPMPAugmentHook.sol";
7+
8+
import {IWeb3Call} from "../../interfaces/v0.8.x/IWeb3Call.sol";
9+
import {Strings} from "@openzeppelin-5.0/contracts/utils/Strings.sol";
10+
11+
/**
12+
* @title InjectBlockTimestamp
13+
* @author Art Blocks Inc.
14+
* @notice This hook injects the current block timestamp into a token's PMPs.
15+
*/
16+
contract InjectBlockTimestamp is AbstractPMPAugmentHook {
17+
using Strings for uint256;
18+
19+
/**
20+
* @notice Augment the token parameters for a given token.
21+
* Appends the block timestamp into a tokens PMPs.
22+
* @dev This hook is called when a token's PMPs are read.
23+
* @dev This must return all desired tokenParams, not just additional data.
24+
* @param tokenParams The token parameters for the queried token.
25+
* @return augmentedTokenParams The augmented token parameters.
26+
*/
27+
function onTokenPMPReadAugmentation(
28+
address /* coreContract */,
29+
uint256 /* tokenId */,
30+
IWeb3Call.TokenParam[] calldata tokenParams
31+
)
32+
external
33+
view
34+
override
35+
returns (IWeb3Call.TokenParam[] memory augmentedTokenParams)
36+
{
37+
// create a new tokenParam array with one extra element
38+
uint256 originalLength = tokenParams.length;
39+
uint256 newLength = originalLength + 1;
40+
augmentedTokenParams = new IWeb3Call.TokenParam[](newLength);
41+
42+
// copy the original tokenParams into the new array
43+
for (uint256 i = 0; i < originalLength; i++) {
44+
augmentedTokenParams[i] = tokenParams[i];
45+
}
46+
47+
// get + inject the block timestamp into the new array
48+
uint256 currentTimestamp = block.timestamp;
49+
augmentedTokenParams[originalLength] = IWeb3Call.TokenParam({
50+
key: "blockTimestamp",
51+
value: currentTimestamp.toString()
52+
});
53+
54+
// return the augmented tokenParams
55+
return augmentedTokenParams;
56+
}
57+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { expect } from "chai";
2+
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
3+
import helpers = require("@nomicfoundation/hardhat-network-helpers");
4+
import { ethers } from "hardhat";
5+
import { constants } from "ethers";
6+
import { PMPFixtureConfig, setupPMPFixture } from "../../pmpFixtures";
7+
import { Contract } from "ethers";
8+
import { Logger } from "@ethersproject/logger";
9+
// hide nuisance logs about event overloading
10+
Logger.setLogLevel(Logger.levels.ERROR);
11+
12+
interface T_ConfigWithHook extends PMPFixtureConfig {
13+
hook: Contract;
14+
}
15+
16+
describe("InjectBlockTimestamp", function () {
17+
async function _beforeEach(): Promise<T_ConfigWithHook> {
18+
const config = await loadFixture(setupPMPFixture);
19+
// deploy the hook
20+
const hookFactory = await ethers.getContractFactory("InjectBlockTimestamp");
21+
const hook = await hookFactory.deploy();
22+
await hook.deployed();
23+
// configure the hook on project zero
24+
await config.pmp.connect(config.accounts.artist).configureProjectHooks(
25+
config.genArt721Core.address,
26+
config.projectZero,
27+
constants.AddressZero, // tokenPMPPostConfigHook
28+
hook.address // tokenPMPReadAugmentationHook
29+
);
30+
return {
31+
...config,
32+
hook,
33+
} as T_ConfigWithHook;
34+
}
35+
36+
describe("onTokenPMPReadAugmentation", function () {
37+
it("appends blockTimestamp to empty params", async function () {
38+
const config = await loadFixture(_beforeEach);
39+
40+
const augmentedParams = await config.hook.onTokenPMPReadAugmentation(
41+
config.genArt721Core.address,
42+
config.projectZeroTokenZero,
43+
[]
44+
);
45+
46+
expect(augmentedParams.length).to.equal(1);
47+
expect(augmentedParams[0].key).to.equal("blockTimestamp");
48+
// value should be a numeric string representing a reasonable timestamp
49+
const timestamp = parseInt(augmentedParams[0].value, 10);
50+
expect(timestamp).to.be.greaterThan(0);
51+
});
52+
53+
it("appends blockTimestamp to existing params", async function () {
54+
const config = await loadFixture(_beforeEach);
55+
56+
const augmentedParams = await config.hook.onTokenPMPReadAugmentation(
57+
config.genArt721Core.address,
58+
config.projectZeroTokenZero,
59+
[
60+
{ key: "param1", value: "value1" },
61+
{ key: "param2", value: "value2" },
62+
]
63+
);
64+
65+
expect(augmentedParams.length).to.equal(3);
66+
expect(augmentedParams[0].key).to.equal("param1");
67+
expect(augmentedParams[0].value).to.equal("value1");
68+
expect(augmentedParams[1].key).to.equal("param2");
69+
expect(augmentedParams[1].value).to.equal("value2");
70+
expect(augmentedParams[2].key).to.equal("blockTimestamp");
71+
const timestamp = parseInt(augmentedParams[2].value, 10);
72+
expect(timestamp).to.be.greaterThan(0);
73+
});
74+
75+
it("returns different timestamps after time passes", async function () {
76+
const config = await loadFixture(_beforeEach);
77+
78+
const params1 = await config.hook.onTokenPMPReadAugmentation(
79+
config.genArt721Core.address,
80+
config.projectZeroTokenZero,
81+
[]
82+
);
83+
const timestamp1 = parseInt(params1[0].value, 10);
84+
85+
// advance time by 60 seconds
86+
await helpers.time.increase(60);
87+
88+
const params2 = await config.hook.onTokenPMPReadAugmentation(
89+
config.genArt721Core.address,
90+
config.projectZeroTokenZero,
91+
[]
92+
);
93+
const timestamp2 = parseInt(params2[0].value, 10);
94+
95+
expect(timestamp2).to.be.greaterThan(timestamp1);
96+
expect(timestamp2 - timestamp1).to.be.at.least(60);
97+
});
98+
});
99+
100+
describe("Integration with PMP system", function () {
101+
it("returns blockTimestamp via PMP getTokenParams", async function () {
102+
const config = await loadFixture(_beforeEach);
103+
104+
const params = await config.pmp.getTokenParams(
105+
config.genArt721Core.address,
106+
config.projectZeroTokenZero
107+
);
108+
109+
// should have at least the blockTimestamp param
110+
const timestampParam = params.find(
111+
(p: { key: string }) => p.key === "blockTimestamp"
112+
);
113+
expect(timestampParam).to.not.be.undefined;
114+
const timestamp = parseInt(timestampParam!.value, 10);
115+
expect(timestamp).to.be.greaterThan(0);
116+
});
117+
});
118+
119+
describe("supportsInterface", function () {
120+
it("supports IPMPAugmentHook interface", async function () {
121+
const config = await loadFixture(_beforeEach);
122+
// IPMPAugmentHook interface ID
123+
const IPMPAugmentHookInterfaceId = "0x58f8699f";
124+
expect(await config.hook.supportsInterface(IPMPAugmentHookInterfaceId)).to
125+
.be.true;
126+
});
127+
128+
it("supports ERC165 interface", async function () {
129+
const config = await loadFixture(_beforeEach);
130+
const IERC165InterfaceId = "0x01ffc9a7";
131+
expect(await config.hook.supportsInterface(IERC165InterfaceId)).to.be
132+
.true;
133+
});
134+
135+
it("does not support random interface", async function () {
136+
const config = await loadFixture(_beforeEach);
137+
expect(await config.hook.supportsInterface("0xdeadbeef")).to.be.false;
138+
});
139+
});
140+
});

0 commit comments

Comments
 (0)