Skip to content

Commit

Permalink
Add paid distributor (#24)
Browse files Browse the repository at this point in the history
* added paid distributor contract

* added changeset

* happy linter - happy life

* linting fixes & improvements
  • Loading branch information
peersky authored Nov 19, 2024
1 parent 11080de commit 2b2da27
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-oranges-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@peeramid-labs/eds": minor
---

added paid distributor contract
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"test": "export NODE_ENV=TEST && pnpm hardhat test",
"test:parallel": "export NODE_ENV=TEST && pnpm hardhat test --parallel",
"build": "pnpm hardhat compile",
"lint:sol": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
"lint:sol:fix": "prettier --loglevel warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
"lint:sol": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
"lint:sol:fix": "prettier --log-level warn --ignore-path .prettierignore '{src,test}/**/*.sol' --write",
"version": "pnpm lint:fix && pnpm build && changeset version",
"release": "pnpm build && changeset publish"
},
Expand Down
42 changes: 25 additions & 17 deletions src/abstracts/Distributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
}

using EnumerableSet for EnumerableSet.Bytes32Set;
EnumerableSet.Bytes32Set private distirbutionsSet;
EnumerableSet.Bytes32Set private distributionsSet;
mapping(address instance => uint256 instanceId) private instanceIds;
mapping(uint256 instance => bytes32 distributorsId) public distributionOf;
mapping(bytes32 distributorsId => DistributionComponent distirbution) public distributionComponents;
Expand All @@ -38,7 +38,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
uint256 public numInstances;
// @inheritdoc IDistributor
function getDistributions() external view returns (bytes32[] memory) {
return distirbutionsSet.values();
return distributionsSet.values();
}
// @inheritdoc IDistributor
function getDistributionId(address instance) external view virtual returns (bytes32) {
Expand All @@ -58,36 +58,44 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
address repository,
address initializer,
LibSemver.VersionRequirement memory requirement
) internal {
) internal virtual returns (bytes32 distributorId) {
if (!ERC165Checker.supportsInterface(address(repository), type(IRepository).interfaceId)) {
revert InvalidRepository(repository);
}
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
distributorId = keccak256(abi.encode(repository, initializer));
if (LibSemver.toUint256(requirement.version) == 0) {
revert InvalidVersionRequested(distributorId, LibSemver.toString(requirement.version));
}
_newDistriubutionRecord(distributorId, repository, initializer);
_newDistributionRecord(distributorId, repository, initializer);
versionRequirements[distributorId] = requirement;
emit VersionChanged(distributorId, requirement, requirement);
}

function _newDistriubutionRecord(bytes32 distributorId, address source, address initializer) private {
if (distirbutionsSet.contains(distributorId)) revert DistributionExists(distributorId);
distirbutionsSet.add(distributorId);
function calculateDistributorId(address repository, address initializer) public pure returns (bytes32) {
return keccak256(abi.encode(repository, initializer));
}

function calculateDistributorId(bytes32 sourceId, address initializer) public pure returns (bytes32) {
return keccak256(abi.encode(sourceId, initializer));
}

function _newDistributionRecord(bytes32 distributorId, address source, address initializer) private {
if (distributionsSet.contains(distributorId)) revert DistributionExists(distributorId);
distributionsSet.add(distributorId);
distributionComponents[distributorId] = DistributionComponent(source, initializer);
emit DistributionAdded(distributorId, source, initializer);
}
function _addDistribution(bytes32 id, address initializerAddress) internal {
function _addDistribution(bytes32 id, address initializerAddress) internal virtual returns (bytes32 distributorId) {
ICodeIndex codeIndex = getContractsIndex();
address distributionLocation = codeIndex.get(id);
if (distributionLocation == address(0)) revert DistributionNotFound(id);
bytes32 distributorsId = keccak256(abi.encode(id, initializerAddress));
_newDistriubutionRecord(distributorsId, distributionLocation, initializerAddress);
distributorId = keccak256(abi.encode(id, initializerAddress));
_newDistributionRecord(distributorId, distributionLocation, initializerAddress);
}

function _removeDistribution(bytes32 distributorsId) internal virtual {
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
distirbutionsSet.remove(distributorsId);
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
distributionsSet.remove(distributorsId);
delete distributionComponents[distributorsId];
delete versionRequirements[distributorsId];
emit DistributionRemoved(distributorsId);
Expand All @@ -101,7 +109,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
bytes32 distributorsId,
bytes memory args
) internal virtual returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion) {
if (!distirbutionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
if (!distributionsSet.contains(distributorsId)) revert DistributionNotFound(distributorsId);
DistributionComponent memory distributionComponent = distributionComponents[distributorsId];
LibSemver.VersionRequirement memory versionRequirement = versionRequirements[distributorsId];

Expand Down Expand Up @@ -182,7 +190,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
if (
distributorsId != bytes32(0) &&
getInstanceId(target) == instanceId &&
distirbutionsSet.contains(distributorsId)
distributionsSet.contains(distributorsId)
) {
// ToDo: This check could be based on DistributionOf, hence allowing cross-instance calls
// Use layerConfig to allow client to configure requirement for the call
Expand All @@ -209,7 +217,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
address target = config.length > 0 ? abi.decode(config, (address)) : msg.sender;
bytes32 distributorsId = distributionOf[getInstanceId(maybeInstance)];
uint256 instanceId = getInstanceId(maybeInstance);
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distirbutionsSet.contains(distributorsId)) {
if ((getInstanceId(target) != getInstanceId(maybeInstance)) && distributionsSet.contains(distributorsId)) {
revert InvalidInstance(maybeInstance);
}
if (!LibSemver.compare(instanceVersions[instanceId], versionRequirements[distributorsId])) {
Expand All @@ -222,7 +230,7 @@ abstract contract Distributor is IDistributor, CodeIndexer, ERC165 {
}

function _changeVersion(bytes32 distributionId, LibSemver.VersionRequirement memory newRequirement) internal {
if (!distirbutionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
if (!distributionsSet.contains(distributionId)) revert DistributionNotFound(distributionId);
LibSemver.VersionRequirement memory oldRequirement = versionRequirements[distributionId];
if (LibSemver.toUint256(oldRequirement.version) == 0) {
revert UniversionedDistribution(distributionId);
Expand Down
57 changes: 57 additions & 0 deletions src/abstracts/TokenizedDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./Distributor.sol";

abstract contract TokenizedDistributor is Distributor {
event InstantiationCostChanged(bytes32 indexed id, uint256 cost);
IERC20 public paymentToken;
address public _beneficiary;
mapping(bytes32 id => uint256) public instantiationCosts;
uint256 public defaultInstantiationCost;
constructor(IERC20 token, uint256 defaultCost, address beneficiary) Distributor() {
paymentToken = token;
defaultInstantiationCost = defaultCost;
_beneficiary = beneficiary;
}

/**
* @notice Sets instantiation cost on a specific instantiation id
* @param distributorsId distributors id
* @param cost cost of instantiation
*/
function _setInstantiationCost(bytes32 distributorsId, uint256 cost) internal {
instantiationCosts[distributorsId] = cost;
emit InstantiationCostChanged(distributorsId, cost);
}

/**
* @inheritdoc Distributor
*/
function _addDistribution(
bytes32 id,
address initializerAddress
) internal override returns (bytes32 distributorsId) {
distributorsId = super._addDistribution(id, initializerAddress);
_setInstantiationCost(distributorsId, defaultInstantiationCost);
}

/**
* @inheritdoc Distributor
*/
function _instantiate(
bytes32 distributorsId,
bytes memory args
)
internal
virtual
override
returns (address[] memory instances, bytes32 distributionName, uint256 distributionVersion)
{
paymentToken.transferFrom(msg.sender, _beneficiary, instantiationCosts[distributorsId]);
return super._instantiate(distributorsId, args);
}
}
10 changes: 10 additions & 0 deletions src/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}
94 changes: 94 additions & 0 deletions src/mocks/MockTokenizedDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "../abstracts/TokenizedDistributor.sol";
import "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockTokenizedDistributor is TokenizedDistributor, AccessControlDefaultAdminRules {
constructor(
address defaultAdmin,
IERC20 token,
uint256 defaultCost
) TokenizedDistributor(token, defaultCost, defaultAdmin) AccessControlDefaultAdminRules(3 days, defaultAdmin) {
paymentToken = token;
defaultInstantiationCost = defaultCost;
}

/**
* @notice Adds a new distribution with the given identifier and initializer address.
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
* @param id The unique identifier for the distribution.
* @param initializer The address that initializes the distribution.
*/
function addDistribution(bytes32 id, address initializer) external onlyRole(DEFAULT_ADMIN_ROLE) {
super._addDistribution(id, initializer);
instantiationCosts[keccak256(abi.encode(id, initializer))] = defaultInstantiationCost;
}

/**
* @notice Sets instantiation cost on a specific instantiation id
* @param id distributors id
* @param cost cost of instantiation
*/
function setInstantiationCost(bytes32 id, uint256 cost) public onlyRole(DEFAULT_ADMIN_ROLE) {
super._setInstantiationCost(id, cost);
}

/**
* @notice Instantiates a new contract with the given identifier and arguments.
* @param id The unique identifier for the contract to be instantiated.
* @param args The calldata arguments required for the instantiation process.
* @return srcs An array of instantiated infrastructure
* @return name The name of the instantiated distribution.
* @return version The version number of the instantiated distribution.
*/
function instantiate(
bytes32 id,
bytes calldata args
) external returns (address[] memory srcs, bytes32 name, uint256 version) {
return super._instantiate(id, args);
}

/**
* @notice Removes a distribution entry identified by the given ID.
* @dev This function can only be called by an account with the `DEFAULT_ADMIN_ROLE`.
* @param id The unique identifier of the distribution entry to be removed.
*/
function removeDistribution(bytes32 id) public onlyRole(DEFAULT_ADMIN_ROLE) {
_removeDistribution(id);
}

/**
*
* This function checks if the contract implements the interface defined by ERC165
*
* @param interfaceId The interface identifier, as specified in ERC-165.
* @return `true` if the contract implements `interfaceId` and
* `interfaceId` is not 0xffffffff, `false` otherwise.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(AccessControlDefaultAdminRules, Distributor) returns (bool) {
return
AccessControlDefaultAdminRules.supportsInterface(interfaceId) || Distributor.supportsInterface(interfaceId);
}

function changeVersion(
bytes32 distributionId,
LibSemver.VersionRequirement memory newRequirement
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
super._changeVersion(distributionId, newRequirement);
}

// @inheritdoc IDistributor
function addDistribution(
IRepository repository,
address initializer,
LibSemver.VersionRequirement memory requirement
) external override onlyRole(DEFAULT_ADMIN_ROLE) {
bytes32 distributorId = keccak256(abi.encode(repository, initializer));
instantiationCosts[distributorId] = defaultInstantiationCost;
super._addDistribution(address(repository), initializer, requirement);
}
}
5 changes: 0 additions & 5 deletions test/eds/CloneDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { ethers } from "hardhat";
import { Signer } from "ethers";
import { expect } from "chai";

describe("CloneDistribution", function () {
let cloneDistribution: any;
let owner: Signer;
let addr1: Signer;
let addr2: Signer;

beforeEach(async function () {
const CloneDistribution = await ethers.getContractFactory("MockCloneDistribution");
[owner, addr1, addr2] = await ethers.getSigners();
cloneDistribution = await CloneDistribution.deploy();
await cloneDistribution.deployed();
});
Expand Down
2 changes: 1 addition & 1 deletion test/eds/CodeHashDistribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("CloneHashDistribution", function () {
0
)) as CodeHashDistribution;

const { src, name, version } = await codeHashDistribution.get();
const { name, version } = await codeHashDistribution.get();
expect(ethers.utils.parseBytes32String(name)).to.be.equal("testDistribution");
expect(version).to.be.equal(0);
});
Expand Down
Loading

0 comments on commit 2b2da27

Please sign in to comment.