Skip to content

Commit 2b2da27

Browse files
authored
Add paid distributor (#24)
* added paid distributor contract * added changeset * happy linter - happy life * linting fixes & improvements
1 parent 11080de commit 2b2da27

9 files changed

+296
-25
lines changed

.changeset/orange-oranges-camp.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@peeramid-labs/eds": minor
3+
---
4+
5+
added paid distributor contract

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"test": "export NODE_ENV=TEST && pnpm hardhat test",
1717
"test:parallel": "export NODE_ENV=TEST && pnpm hardhat test --parallel",
1818
"build": "pnpm hardhat compile",
19-
"lint:sol": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
20-
"lint:sol:fix": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
19+
"lint:sol": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
20+
"lint:sol:fix": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
2121
"version": "pnpm lint:fix && pnpm build && changeset version",
2222
"release": "pnpm build && changeset publish"
2323
},

src/abstracts/Distributor.sol

+25-17
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
2828
}
2929

3030
using EnumerableSet for EnumerableSet.Bytes32Set;
31-
EnumerableSet.Bytes32Set private distirbutionsSet;
31+
EnumerableSet.Bytes32Set private distributionsSet;
3232
mapping(address instance => uint256 instanceId) private instanceIds;
3333
mapping(uint256 instance => bytes32 distributorsId) public distributionOf;
3434
mapping(bytes32 distributorsId => DistributionComponent distirbution) public distributionComponents;
@@ -38,7 +38,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
3838
uint256 public numInstances;
3939
// @inheritdoc IDistributor
4040
function getDistributions() external view returns (bytes32[] memory) {
41-
return distirbutionsSet.values();
41+
return distributionsSet.values();
4242
}
4343
// @inheritdoc IDistributor
4444
function getDistributionId(address instance) external view virtual returns (bytes32) {
@@ -58,36 +58,44 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
5858
address repository,
5959
address initializer,
6060
LibSemver.VersionRequirement memory requirement
61-
) internal {
61+
) internal virtual returns (bytes32 distributorId) {
6262
if (!ERC165Checker.supportsInterface(address(repository), type(IRepository).interfaceId)) {
6363
revert InvalidRepository(repository);
6464
}
65-
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
65+
distributorId = keccak256(abi.encode(repository, initializer));
6666
if (LibSemver.toUint256(requirement.version) == 0) {
6767
revert InvalidVersionRequested(distributorId, LibSemver.toString(requirement.version));
6868
}
69-
_newDistriubutionRecord(distributorId, repository, initializer);
69+
_newDistributionRecord(distributorId, repository, initializer);
7070
versionRequirements[distributorId] = requirement;
7171
emit VersionChanged(distributorId, requirement, requirement);
7272
}
7373

74-
function _newDistriubutionRecord(bytes32 distributorId, address source, address initializer) private {
75-
if (distirbutionsSet.contains(distributorId)) revert DistributionExists(distributorId);
76-
distirbutionsSet.add(distributorId);
74+
function calculateDistributorId(address repository, address initializer) public pure returns (bytes32) {
75+
return keccak256(abi.encode(repository, initializer));
76+
}
77+
78+
function calculateDistributorId(bytes32 sourceId, address initializer) public pure returns (bytes32) {
79+
return keccak256(abi.encode(sourceId, initializer));
80+
}
81+
82+
function _newDistributionRecord(bytes32 distributorId, address source, address initializer) private {
83+
if (distributionsSet.contains(distributorId)) revert DistributionExists(distributorId);
84+
distributionsSet.add(distributorId);
7785
distributionComponents[distributorId] = DistributionComponent(source, initializer);
7886
emit DistributionAdded(distributorId, source, initializer);
7987
}
80-
function _addDistribution(bytes32 id, address initializerAddress) internal {
88+
function _addDistribution(bytes32 id, address initializerAddress) internal virtual returns (bytes32 distributorId) {
8189
ICodeIndex codeIndex = getContractsIndex();
8290
address distributionLocation = codeIndex.get(id);
8391
if (distributionLocation == address(0)) revert DistributionNotFound(id);
84-
bytes32 distributorsId = keccak256(abi.encode(id, initializerAddress));
85-
_newDistriubutionRecord(distributorsId, distributionLocation, initializerAddress);
92+
distributorId = keccak256(abi.encode(id, initializerAddress));
93+
_newDistributionRecord(distributorId, distributionLocation, initializerAddress);
8694
}
8795

8896
function _removeDistribution(bytes32 distributorsId) internal virtual {
89-
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
90-
distirbutionsSet.remove(distributorsId);
97+
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
98+
distributionsSet.remove(distributorsId);
9199
delete distributionComponents[distributorsId];
92100
delete versionRequirements[distributorsId];
93101
emit DistributionRemoved(distributorsId);
@@ -101,7 +109,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
101109
bytes32 distributorsId,
102110
bytes memory args
103111
) internal virtual returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
104-
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
112+
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
105113
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
106114
LibSemver.VersionRequirement memory versionRequirement = versionRequirements[distributorsId];
107115

@@ -182,7 +190,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
182190
if (
183191
distributorsId != bytes32(0) &&
184192
getInstanceId(target) == instanceId &&
185-
distirbutionsSet.contains(distributorsId)
193+
distributionsSet.contains(distributorsId)
186194
) {
187195
// ToDo: This check could be based on DistributionOf, hence allowing cross-instance calls
188196
// Use layerConfig to allow client to configure requirement for the call
@@ -209,7 +217,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
209217
address target = config.length > 0 ? abi.decode(config, (address)) : msg.sender;
210218
bytes32 distributorsId = distributionOf[getInstanceId(maybeInstance)];
211219
uint256 instanceId = getInstanceId(maybeInstance);
212-
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distirbutionsSet.contains(distributorsId)) {
220+
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distributionsSet.contains(distributorsId)) {
213221
revert InvalidInstance(maybeInstance);
214222
}
215223
if (!LibSemver.compare(instanceVersions[instanceId], versionRequirements[distributorsId])) {
@@ -222,7 +230,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
222230
}
223231

224232
function _changeVersion(bytes32 distributionId, LibSemver.VersionRequirement memory newRequirement) internal {
225-
if (!distirbutionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
233+
if (!distributionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
226234
LibSemver.VersionRequirement memory oldRequirement = versionRequirements[distributionId];
227235
if (LibSemver.toUint256(oldRequirement.version) == 0) {
228236
revert UniversionedDistribution(distributionId);
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity >=0.8.0 <0.9.0;
4+
5+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
7+
import "./Distributor.sol";
8+
9+
abstract contract TokenizedDistributor is Distributor {
10+
event InstantiationCostChanged(bytes32 indexed id, uint256 cost);
11+
IERC20 public paymentToken;
12+
address public _beneficiary;
13+
mapping(bytes32 id => uint256) public instantiationCosts;
14+
uint256 public defaultInstantiationCost;
15+
constructor(IERC20 token, uint256 defaultCost, address beneficiary) Distributor() {
16+
paymentToken = token;
17+
defaultInstantiationCost = defaultCost;
18+
_beneficiary = beneficiary;
19+
}
20+
21+
/**
22+
* @notice Sets instantiation cost on a specific instantiation id
23+
* @param distributorsId distributors id
24+
* @param cost cost of instantiation
25+
*/
26+
function _setInstantiationCost(bytes32 distributorsId, uint256 cost) internal {
27+
instantiationCosts[distributorsId] = cost;
28+
emit InstantiationCostChanged(distributorsId, cost);
29+
}
30+
31+
/**
32+
* @inheritdoc Distributor
33+
*/
34+
function _addDistribution(
35+
bytes32 id,
36+
address initializerAddress
37+
) internal override returns (bytes32 distributorsId) {
38+
distributorsId = super._addDistribution(id, initializerAddress);
39+
_setInstantiationCost(distributorsId, defaultInstantiationCost);
40+
}
41+
42+
/**
43+
* @inheritdoc Distributor
44+
*/
45+
function _instantiate(
46+
bytes32 distributorsId,
47+
bytes memory args
48+
)
49+
internal
50+
virtual
51+
override
52+
returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion)
53+
{
54+
paymentToken.transferFrom(msg.sender, _beneficiary, instantiationCosts[distributorsId]);
55+
return super._instantiate(distributorsId, args);
56+
}
57+
}

src/mocks/MockERC20.sol

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
6+
contract MockERC20 is ERC20 {
7+
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
8+
_mint(msg.sender, initialSupply);
9+
}
10+
}
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
import "../abstracts/TokenizedDistributor.sol";
5+
import "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
6+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
8+
contract MockTokenizedDistributor is TokenizedDistributor, AccessControlDefaultAdminRules {
9+
constructor(
10+
address defaultAdmin,
11+
IERC20 token,
12+
uint256 defaultCost
13+
) TokenizedDistributor(token, defaultCost, defaultAdmin) AccessControlDefaultAdminRules(3 days, defaultAdmin) {
14+
paymentToken = token;
15+
defaultInstantiationCost = defaultCost;
16+
}
17+
18+
/**
19+
* @notice Adds a new distribution with the given identifier and initializer address.
20+
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
21+
* @param id The unique identifier for the distribution.
22+
* @param initializer The address that initializes the distribution.
23+
*/
24+
function addDistribution(bytes32 id, address initializer) external onlyRole(DEFAULT_ADMIN_ROLE) {
25+
super._addDistribution(id, initializer);
26+
instantiationCosts[keccak256(abi.encode(id, initializer))] = defaultInstantiationCost;
27+
}
28+
29+
/**
30+
* @notice Sets instantiation cost on a specific instantiation id
31+
* @param id distributors id
32+
* @param cost cost of instantiation
33+
*/
34+
function setInstantiationCost(bytes32 id, uint256 cost) public onlyRole(DEFAULT_ADMIN_ROLE) {
35+
super._setInstantiationCost(id, cost);
36+
}
37+
38+
/**
39+
* @notice Instantiates a new contract with the given identifier and arguments.
40+
* @param id The unique identifier for the contract to be instantiated.
41+
* @param args The calldata arguments required for the instantiation process.
42+
* @return srcs An array of instantiated infrastructure
43+
* @return name The name of the instantiated distribution.
44+
* @return version The version number of the instantiated distribution.
45+
*/
46+
function instantiate(
47+
bytes32 id,
48+
bytes calldata args
49+
) external returns (address[] memory srcs, bytes32 name, uint256 version) {
50+
return super._instantiate(id, args);
51+
}
52+
53+
/**
54+
* @notice Removes a distribution entry identified by the given ID.
55+
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
56+
* @param id The unique identifier of the distribution entry to be removed.
57+
*/
58+
function removeDistribution(bytes32 id) public onlyRole(DEFAULT_ADMIN_ROLE) {
59+
_removeDistribution(id);
60+
}
61+
62+
/**
63+
*
64+
* This function checks if the contract implements the interface defined by ERC165
65+
*
66+
* @param interfaceId The interface identifier, as specified in ERC-165.
67+
* @return `true` if the contract implements `interfaceId` and
68+
* `interfaceId` is not 0xffffffff, `false` otherwise.
69+
*/
70+
function supportsInterface(
71+
bytes4 interfaceId
72+
) public view virtual override(AccessControlDefaultAdminRules, Distributor) returns (bool) {
73+
return
74+
AccessControlDefaultAdminRules.supportsInterface(interfaceId) || Distributor.supportsInterface(interfaceId);
75+
}
76+
77+
function changeVersion(
78+
bytes32 distributionId,
79+
LibSemver.VersionRequirement memory newRequirement
80+
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
81+
super._changeVersion(distributionId, newRequirement);
82+
}
83+
84+
// @inheritdoc IDistributor
85+
function addDistribution(
86+
IRepository repository,
87+
address initializer,
88+
LibSemver.VersionRequirement memory requirement
89+
) external override onlyRole(DEFAULT_ADMIN_ROLE) {
90+
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
91+
instantiationCosts[distributorId] = defaultInstantiationCost;
92+
super._addDistribution(address(repository), initializer, requirement);
93+
}
94+
}

test/eds/CloneDistribution.ts

-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { ethers } from "hardhat";
2-
import { Signer } from "ethers";
32
import { expect } from "chai";
43

54
describe("CloneDistribution", function () {
65
let cloneDistribution: any;
7-
let owner: Signer;
8-
let addr1: Signer;
9-
let addr2: Signer;
106

117
beforeEach(async function () {
128
const CloneDistribution = await ethers.getContractFactory("MockCloneDistribution");
13-
[owner, addr1, addr2] = await ethers.getSigners();
149
cloneDistribution = await CloneDistribution.deploy();
1510
await cloneDistribution.deployed();
1611
});

test/eds/CodeHashDistribution.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe("CloneHashDistribution", function () {
8080
0
8181
)) as CodeHashDistribution;
8282

83-
const { src, name, version } = await codeHashDistribution.get();
83+
const { name, version } = await codeHashDistribution.get();
8484
expect(ethers.utils.parseBytes32String(name)).to.be.equal("testDistribution");
8585
expect(version).to.be.equal(0);
8686
});

0 commit comments

Comments
 (0)