Skip to content

Commit

Permalink
Add Factory for payments
Browse files Browse the repository at this point in the history
  • Loading branch information
ScreamingHawk committed Aug 7, 2024
1 parent de5fd16 commit 9b9525d
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 10 deletions.
4 changes: 2 additions & 2 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import util from 'util'
import {
BUILD_DIR,
DEPLOYABLE_CONTRACT_NAMES,
TOKEN_CONTRACT_NAMES,
PROXIED_TOKEN_CONTRACT_NAMES,
} from './constants'
const exec = util.promisify(execNonPromise)

Expand All @@ -27,7 +27,7 @@ const main = async () => {
// Create the compiler input files
for (const solFile of [
...DEPLOYABLE_CONTRACT_NAMES,
...TOKEN_CONTRACT_NAMES,
...PROXIED_TOKEN_CONTRACT_NAMES,
'TransparentUpgradeableBeaconProxy',
'UpgradeableBeacon',
]) {
Expand Down
5 changes: 3 additions & 2 deletions scripts/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ export const DEPLOYABLE_CONTRACT_NAMES = [
'ERC721SaleFactory',
'ERC1155ItemsFactory',
'ERC1155SaleFactory',
'Payments',
'PaymentsFactory',
]
export const TOKEN_CONTRACT_NAMES = [
export const PROXIED_TOKEN_CONTRACT_NAMES = [
'ERC20Items',
'ERC721Items',
'ERC721Sale',
'ERC1155Items',
'ERC1155Sale',
'Payments',
]
4 changes: 2 additions & 2 deletions scripts/outputSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TOKEN_CONTRACT_NAMES } from './constants'
import { PROXIED_TOKEN_CONTRACT_NAMES } from './constants'

const { spawn } = require('child_process')

Expand Down Expand Up @@ -31,4 +31,4 @@ const outputSelectors = (contractName: string) => {
})
}

TOKEN_CONTRACT_NAMES.forEach(outputSelectors)
PROXIED_TOKEN_CONTRACT_NAMES.forEach(outputSelectors)
3 changes: 3 additions & 0 deletions src/payments/IPayments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ interface IPaymentsFunctions {
}

interface IPaymentsSignals {
/// @notice Emitted when contract is already initialized.
error InvalidInitialization();

/// @notice Emitted when a payment is already accepted. This prevents double spending.
error PaymentAlreadyAccepted();

Expand Down
36 changes: 36 additions & 0 deletions src/payments/IPaymentsFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IPaymentsFactoryFunctions {
/**
* Creates a Payments proxy contract
* @param proxyOwner The owner of the Payments proxy
* @param paymentsOwner The owner of the Payments implementation
* @param paymentsSigner The signer of the Payments implementation
* @return proxyAddr The address of the Payments proxy
*/
function deploy(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
returns (address proxyAddr);

/**
* Computes the address of a proxy instance.
* @param proxyOwner The owner of the Payments proxy
* @param paymentsOwner The owner of the Payments implementation
* @param paymentsSigner The signer of the Payments implementation
* @return proxyAddr The address of the Payments proxy
*/
function determineAddress(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
returns (address proxyAddr);
}

interface IPaymentsFactorySignals {
/**
* Event emitted when a new Payments proxy contract is deployed.
* @param proxyAddr The address of the deployed proxy.
*/
event PaymentsDeployed(address proxyAddr);
}

interface IPaymentsFactory is IPaymentsFactoryFunctions, IPaymentsFactorySignals {}
16 changes: 15 additions & 1 deletion src/payments/Payments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
contract Payments is Ownable, IPayments, IERC165 {
using SignatureValidator for bytes32;

bool private _initialized;

address public signer;

// Payment accepted. Works as a nonce.
mapping(uint256 => bool) public paymentAccepted;

constructor(address _owner, address _signer) {
/**
* Initialize the contract.
* @param _owner Owner address
* @param _signer Signer address
* @dev This should be called immediately after deployment.
*/
function initialize(address _owner, address _signer) public virtual {
if (_initialized) {
revert InvalidInitialization();
}

Ownable._transferOwnership(_owner);
signer = _signer;

_initialized = true;
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/payments/PaymentsFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import {Payments} from "@0xsequence/contracts-library/payments/Payments.sol";
import {
IPaymentsFactory, IPaymentsFactoryFunctions
} from "@0xsequence/contracts-library/payments/IPaymentsFactory.sol";
import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol";

/**
* Deployer of Payments proxies.
*/
contract PaymentsFactory is IPaymentsFactory, SequenceProxyFactory {
/**
* Creates an Payments Factory.
* @param factoryOwner The owner of the Payments Factory
*/
constructor(address factoryOwner) {
Payments impl = new Payments();
SequenceProxyFactory._initialize(address(impl), factoryOwner);
}

/// @inheritdoc IPaymentsFactoryFunctions
function deploy(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
returns (address proxyAddr)
{
bytes32 salt = keccak256(abi.encode(paymentsOwner, paymentsSigner));
proxyAddr = _createProxy(salt, proxyOwner, "");
Payments(proxyAddr).initialize(paymentsOwner, paymentsSigner);
emit PaymentsDeployed(proxyAddr);
return proxyAddr;
}

/// @inheritdoc IPaymentsFactoryFunctions
function determineAddress(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
view
returns (address proxyAddr)
{
bytes32 salt = keccak256(abi.encode(paymentsOwner, paymentsSigner));
return _computeProxyAddress(salt, proxyOwner, "");
}
}
29 changes: 26 additions & 3 deletions test/payments/Payments.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {TestHelper} from "../TestHelper.sol";

import {PaymentsFactory} from "src/payments/PaymentsFactory.sol";
import {Payments, IERC165} from "src/payments/Payments.sol";
import {IPayments, IPaymentsFunctions, IPaymentsSignals} from "src/payments/IPayments.sol";

Expand All @@ -12,7 +13,7 @@ import {ERC721Mock} from "test/_mocks/ERC721Mock.sol";
import {IGenericToken} from "test/_mocks/IGenericToken.sol";
import {ERC1271Mock} from "test/_mocks/ERC1271Mock.sol";

contract PaymentsTest is Test, IPaymentsSignals {
contract PaymentsTest is TestHelper, IPaymentsSignals {
Payments public payments;
address public owner;
address public signer;
Expand All @@ -26,7 +27,8 @@ contract PaymentsTest is Test, IPaymentsSignals {
function setUp() public {
owner = makeAddr("owner");
(signer, signerPk) = makeAddrAndKey("signer");
payments = new Payments(owner, signer);
PaymentsFactory factory = new PaymentsFactory(owner);
payments = Payments(factory.deploy(owner, owner, signer));

erc20 = new ERC20Mock(address(this));
erc721 = new ERC721Mock(address(this), "baseURI");
Expand Down Expand Up @@ -107,6 +109,27 @@ contract PaymentsTest is Test, IPaymentsSignals {
}
}

/**
* Test all public selectors for collisions against the proxy admin functions.
* @dev yarn ts-node scripts/outputSelectors.ts
*/
function testSelectorCollision() public pure {
checkSelectorCollision(0x0e6fe11f); // hashChainedCallDetails((address,bytes))
checkSelectorCollision(0x98c3065f); // hashPaymentDetails((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes)))
checkSelectorCollision(0x485cc955); // initialize(address,address)
checkSelectorCollision(0x579a97e6); // isValidChainedCallSignature((address,bytes),bytes)
checkSelectorCollision(0x7b8bdc8e); // isValidPaymentSignature((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes)),bytes)
checkSelectorCollision(0xdecfb3b2); // makePayment((uint256,address,uint8,address,uint256,(address,uint256)[],uint64,string,(address,bytes)),bytes)
checkSelectorCollision(0x8da5cb5b); // owner()
checkSelectorCollision(0x3a63b803); // paymentAccepted(uint256)
checkSelectorCollision(0xb2238700); // performChainedCall((address,bytes),bytes)
checkSelectorCollision(0x715018a6); // renounceOwnership()
checkSelectorCollision(0x238ac933); // signer()
checkSelectorCollision(0x01ffc9a7); // supportsInterface(bytes4)
checkSelectorCollision(0xf2fde38b); // transferOwnership(address)
checkSelectorCollision(0xa7ecd37e); // updateSigner(address)
}

function testMakePaymentSuccess(address caller, DetailsInput calldata input, bool isERC1271)
public
safeAddress(caller)
Expand Down

0 comments on commit 9b9525d

Please sign in to comment.