Skip to content

Commit 84bd28f

Browse files
Fix for contract published metadata registering + enable linking to previous implementation (#205)
* fix multiple version backtracking + introduce linkback mechanism * add unit test * add another test for linked publishers * fix list appending logic
1 parent 745e94f commit 84bd28f

File tree

5 files changed

+232
-15
lines changed

5 files changed

+232
-15
lines changed

contracts/ContractPublisher.sol

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
1919

2020
/// @notice Whether the contract publisher is paused.
2121
bool public isPaused;
22+
IContractPublisher public prevPublisher;
2223

2324
/*///////////////////////////////////////////////////////////////
2425
Mappings
@@ -49,8 +50,9 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
4950
_;
5051
}
5152

52-
constructor(address _trustedForwarder) ERC2771Context(_trustedForwarder) {
53+
constructor(address _trustedForwarder, IContractPublisher _prevPublisher) ERC2771Context(_trustedForwarder) {
5354
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
55+
prevPublisher = _prevPublisher;
5456
}
5557

5658
/*///////////////////////////////////////////////////////////////
@@ -63,13 +65,19 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
6365
view
6466
returns (CustomContractInstance[] memory published)
6567
{
66-
uint256 total = EnumerableSet.length(contractsOfPublisher[_publisher].contractIds);
67-
68+
CustomContractInstance[] memory linkedData = prevPublisher.getAllPublishedContracts(_publisher);
69+
uint256 currentTotal = EnumerableSet.length(contractsOfPublisher[_publisher].contractIds);
70+
uint256 prevTotal = linkedData.length;
71+
uint256 total = prevTotal + currentTotal;
6872
published = new CustomContractInstance[](total);
69-
70-
for (uint256 i = 0; i < total; i += 1) {
73+
// fill in previously published contracts
74+
for (uint256 i = 0; i < prevTotal; i += 1) {
75+
published[i] = linkedData[i];
76+
}
77+
// fill in current published contracts
78+
for (uint256 i = 0; i < currentTotal; i += 1) {
7179
bytes32 contractId = EnumerableSet.at(contractsOfPublisher[_publisher].contractIds, i);
72-
published[i] = contractsOfPublisher[_publisher].contracts[contractId].latest;
80+
published[i + prevTotal] = contractsOfPublisher[_publisher].contracts[contractId].latest;
7381
}
7482
}
7583

@@ -79,13 +87,25 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
7987
view
8088
returns (CustomContractInstance[] memory published)
8189
{
90+
CustomContractInstance[] memory linkedVersions = prevPublisher.getPublishedContractVersions(
91+
_publisher,
92+
_contractId
93+
);
94+
uint256 prevTotal = linkedVersions.length;
95+
8296
bytes32 id = keccak256(bytes(_contractId));
83-
uint256 total = contractsOfPublisher[_publisher].contracts[id].total;
97+
uint256 currentTotal = contractsOfPublisher[_publisher].contracts[id].total;
98+
uint256 total = prevTotal + currentTotal;
8499

85100
published = new CustomContractInstance[](total);
86101

87-
for (uint256 i = 0; i < total; i += 1) {
88-
published[i] = contractsOfPublisher[_publisher].contracts[id].instances[i];
102+
// fill in previously published contracts
103+
for (uint256 i = 0; i < prevTotal; i += 1) {
104+
published[i] = linkedVersions[i];
105+
}
106+
// fill in current published contracts
107+
for (uint256 i = 0; i < currentTotal; i += 1) {
108+
published[i + prevTotal] = contractsOfPublisher[_publisher].contracts[id].instances[i];
89109
}
90110
}
91111

@@ -96,6 +116,10 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
96116
returns (CustomContractInstance memory published)
97117
{
98118
published = contractsOfPublisher[_publisher].contracts[keccak256(bytes(_contractId))].latest;
119+
// if not found, check the previous publisher
120+
if (published.publishTimestamp == 0) {
121+
published = prevPublisher.getPublishedContract(_publisher, _contractId);
122+
}
99123
}
100124

101125
/*///////////////////////////////////////////////////////////////
@@ -129,7 +153,7 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
129153
contractsOfPublisher[_publisher].contracts[contractIdInBytes].instances[index] = publishedContract;
130154

131155
uint256 metadataIndex = compilerMetadataUriToPublishedMetadataUris[_compilerMetadataUri].index;
132-
compilerMetadataUriToPublishedMetadataUris[_compilerMetadataUri].uris[index] = _publishMetadataUri;
156+
compilerMetadataUriToPublishedMetadataUris[_compilerMetadataUri].uris[metadataIndex] = _publishMetadataUri;
133157
compilerMetadataUriToPublishedMetadataUris[_compilerMetadataUri].index = metadataIndex + 1;
134158

135159
emit ContractPublished(_msgSender(), _publisher, publishedContract);
@@ -159,6 +183,10 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
159183
// @notice Get a publisher profile uri
160184
function getPublisherProfileUri(address publisher) public view returns (string memory uri) {
161185
uri = profileUriOfPublisher[publisher];
186+
// if not found, check the previous publisher
187+
if (bytes(uri).length == 0) {
188+
uri = prevPublisher.getPublisherProfileUri(publisher);
189+
}
162190
}
163191

164192
/// @notice Retrieve the published metadata URI from a compiler metadata URI
@@ -167,10 +195,20 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE
167195
view
168196
returns (string[] memory publishedMetadataUris)
169197
{
170-
uint256 length = compilerMetadataUriToPublishedMetadataUris[compilerMetadataUri].index;
171-
publishedMetadataUris = new string[](length);
172-
for (uint256 i = 0; i < length; i += 1) {
173-
publishedMetadataUris[i] = compilerMetadataUriToPublishedMetadataUris[compilerMetadataUri].uris[i];
198+
string[] memory linkedUris = prevPublisher.getPublishedUriFromCompilerUri(compilerMetadataUri);
199+
uint256 prevTotal = linkedUris.length;
200+
uint256 currentTotal = compilerMetadataUriToPublishedMetadataUris[compilerMetadataUri].index;
201+
uint256 total = prevTotal + currentTotal;
202+
publishedMetadataUris = new string[](total);
203+
// fill in previously published uris
204+
for (uint256 i = 0; i < prevTotal; i += 1) {
205+
publishedMetadataUris[i] = linkedUris[i];
206+
}
207+
// fill in current published uris
208+
for (uint256 i = 0; i < currentTotal; i += 1) {
209+
publishedMetadataUris[i + prevTotal] = compilerMetadataUriToPublishedMetadataUris[compilerMetadataUri].uris[
210+
i
211+
];
174212
}
175213
}
176214

docs/ContractPublisher.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,23 @@ function multicall(bytes[] data) external nonpayable returns (bytes[] results)
307307
|---|---|---|
308308
| results | bytes[] | undefined
309309

310+
### prevPublisher
311+
312+
```solidity
313+
function prevPublisher() external view returns (contract IContractPublisher)
314+
```
315+
316+
317+
318+
319+
320+
321+
#### Returns
322+
323+
| Name | Type | Description |
324+
|---|---|---|
325+
| _0 | contract IContractPublisher | undefined
326+
310327
### publishContract
311328

312329
```solidity

src/test/ContractPublisher.t.sol

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,59 @@ contract ContractPublisherTest is BaseTest, IContractPublisherData {
149149
);
150150
}
151151

152+
function test_publish_multiple_versions() public {
153+
string memory contractId = "MyContract";
154+
155+
vm.prank(publisher);
156+
byoc.publishContract(
157+
publisher,
158+
contractId,
159+
publishMetadataUri,
160+
compilerMetadataUri,
161+
keccak256(type(MockCustomContract).creationCode),
162+
address(0)
163+
);
164+
string[] memory resolved = byoc.getPublishedUriFromCompilerUri(compilerMetadataUri);
165+
assertEq(resolved.length, 1);
166+
assertEq(resolved[0], publishMetadataUri);
167+
168+
string memory otherUri = "ipfs://abcd";
169+
vm.prank(publisher);
170+
byoc.publishContract(
171+
publisher,
172+
contractId,
173+
publishMetadataUri,
174+
otherUri,
175+
keccak256(type(MockCustomContract).creationCode),
176+
address(0)
177+
);
178+
179+
string[] memory resolved2 = byoc.getPublishedUriFromCompilerUri(otherUri);
180+
assertEq(resolved2.length, 1);
181+
assertEq(resolved2[0], publishMetadataUri);
182+
}
183+
184+
function test_read_from_linked_publisher() public {
185+
IContractPublisher.CustomContractInstance[] memory contracts = byoc.getAllPublishedContracts(publisher);
186+
assertEq(contracts.length, 1);
187+
assertEq(contracts[0].contractId, "MockContract");
188+
189+
string memory contractId = "MyContract";
190+
vm.prank(publisher);
191+
byoc.publishContract(
192+
publisher,
193+
contractId,
194+
publishMetadataUri,
195+
compilerMetadataUri,
196+
keccak256(type(MockCustomContract).creationCode),
197+
address(0)
198+
);
199+
IContractPublisher.CustomContractInstance[] memory contracts2 = byoc.getAllPublishedContracts(publisher);
200+
assertEq(contracts2.length, 2);
201+
assertEq(contracts2[0].contractId, "MockContract");
202+
assertEq(contracts2[1].contractId, "MyContract");
203+
}
204+
152205
// Deprecated
153206
// function test_publish_emit_ContractPublished() public {
154207
// string memory contractId = "MyContract";
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
import "../../../contracts/interfaces/IContractPublisher.sol";
5+
6+
// solhint-disable const-name-snakecase
7+
contract MockContractPublisher is IContractPublisher {
8+
function getAllPublishedContracts(address publisher)
9+
external
10+
view
11+
override
12+
returns (CustomContractInstance[] memory published)
13+
{
14+
CustomContractInstance[] memory mocks = new CustomContractInstance[](1);
15+
mocks[0] = CustomContractInstance(
16+
"MockContract",
17+
123,
18+
"ipfs://mock",
19+
0x0000000000000000000000000000000000000000000000000000000000000001,
20+
address(0x0000000000000000000000000000000000000000)
21+
);
22+
return mocks;
23+
}
24+
25+
/**
26+
* @notice Returns all versions of a published contract.
27+
*
28+
* @param publisher The address of the publisher.
29+
* @param contractId The identifier for a published contract (that can have multiple verisons).
30+
*
31+
* @return published The desired contracts published by the publisher.
32+
*/
33+
function getPublishedContractVersions(address publisher, string memory contractId)
34+
external
35+
view
36+
returns (CustomContractInstance[] memory published)
37+
{
38+
return new CustomContractInstance[](0);
39+
}
40+
41+
/**
42+
* @notice Returns the latest version of a contract published by a publisher.
43+
*
44+
* @param publisher The address of the publisher.
45+
* @param contractId The identifier for a published contract (that can have multiple verisons).
46+
*
47+
* @return published The desired contract published by the publisher.
48+
*/
49+
function getPublishedContract(address publisher, string memory contractId)
50+
external
51+
view
52+
returns (CustomContractInstance memory published)
53+
{
54+
return CustomContractInstance("", 0, "", "", address(0));
55+
}
56+
57+
/**
58+
* @notice Let's an account publish a contract.
59+
*
60+
* @param publisher The address of the publisher.
61+
* @param contractId The identifier for a published contract (that can have multiple verisons).
62+
* @param publishMetadataUri The IPFS URI of the publish metadata.
63+
* @param compilerMetadataUri The IPFS URI of the compiler metadata.
64+
* @param bytecodeHash The keccak256 hash of the contract bytecode.
65+
* @param implementation (Optional) An implementation address that proxy contracts / clones can point to. Default value
66+
* if such an implementation does not exist - address(0);
67+
*/
68+
function publishContract(
69+
address publisher,
70+
string memory contractId,
71+
string memory publishMetadataUri,
72+
string memory compilerMetadataUri,
73+
bytes32 bytecodeHash,
74+
address implementation
75+
) external {}
76+
77+
/**
78+
* @notice Lets a publisher unpublish a contract and all its versions.
79+
*
80+
* @param publisher The address of the publisher.
81+
* @param contractId The identifier for a published contract (that can have multiple verisons).
82+
*/
83+
function unpublishContract(address publisher, string memory contractId) external {}
84+
85+
/**
86+
* @notice Lets an account set its publisher profile uri
87+
*/
88+
function setPublisherProfileUri(address publisher, string memory uri) external {}
89+
90+
/**
91+
* @notice Get the publisher profile uri for a given publisher.
92+
*/
93+
function getPublisherProfileUri(address publisher) external view returns (string memory uri) {
94+
return "";
95+
}
96+
97+
/**
98+
* @notice Retrieve the published metadata URI from a compiler metadata URI.
99+
*/
100+
function getPublishedUriFromCompilerUri(string memory compilerMetadataUri)
101+
external
102+
view
103+
returns (string[] memory publishedMetadataUris)
104+
{
105+
return new string[](0);
106+
}
107+
}

src/test/utils/BaseTest.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "../mocks/WETH9.sol";
99
import "../mocks/MockERC20.sol";
1010
import "../mocks/MockERC721.sol";
1111
import "../mocks/MockERC1155.sol";
12+
import "../mocks/MockContractPublisher.sol";
1213
import "contracts/Forwarder.sol";
1314
import "contracts/TWFee.sol";
1415
import "contracts/TWRegistry.sol";
@@ -26,6 +27,7 @@ import { Marketplace } from "contracts/marketplace/Marketplace.sol";
2627
import { VoteERC20 } from "contracts/vote/VoteERC20.sol";
2728
import { SignatureDrop } from "contracts/signature-drop/SignatureDrop.sol";
2829
import { ContractPublisher } from "contracts/ContractPublisher.sol";
30+
import { IContractPublisher } from "contracts/interfaces/IContractPublisher.sol";
2931
import "contracts/mock/Mock.sol";
3032

3133
abstract contract BaseTest is DSTest, Test {
@@ -72,7 +74,7 @@ abstract contract BaseTest is DSTest, Test {
7274
forwarder = address(new Forwarder());
7375
registry = address(new TWRegistry(forwarder));
7476
factory = address(new TWFactory(forwarder, registry));
75-
contractPublisher = address(new ContractPublisher(forwarder));
77+
contractPublisher = address(new ContractPublisher(forwarder, new MockContractPublisher()));
7678
TWRegistry(registry).grantRole(TWRegistry(registry).OPERATOR_ROLE(), factory);
7779
TWRegistry(registry).grantRole(TWRegistry(registry).OPERATOR_ROLE(), contractPublisher);
7880
fee = address(new TWFee(forwarder, factory));

0 commit comments

Comments
 (0)