Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ const config: HardhatUserConfig = {
chainId: 10,
url: `https://mainnet.optimism.io`,
},
ethereum: {
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
chainId: 1,
url: `https://ethereum-rpc.publicnode.com`,
},
arbitrum: {
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
chainId: 42161,
url: `https://arb1.arbitrum.io/rpc`,
},
base: {
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
chainId: 8453,
url: `https://mainnet.base.org`,
},
polygon: {
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
chainId: 137,
url: `https://polygon.api.onfinality.io/public`,
},
amoy: {
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
chainId: 80002,
url: `https://rpc-amoy.polygon.technology`,
},
op_sepolia: {
url: 'https://sepolia.optimism.io',
chainId: 11155420,
Expand Down
51 changes: 29 additions & 22 deletions src/PaymentEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "./PaymentInput.sol";
import "./IEscrowContract.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IPurchaseTracker.sol";
import "./PaymentEscrowAdmins.sol";

/**
* @title PaymentEscrow
Expand All @@ -19,11 +20,11 @@ import "./IPurchaseTracker.sol";
* @author John R. Kosinski
* LoadPipe 2024
*/
contract PaymentEscrow is HasSecurityContext, IEscrowContract
contract PaymentEscrow is PaymentEscrowAdmins, HasSecurityContext, IEscrowContract
{
ISystemSettings private settings;
mapping(bytes32 => Payment) private payments;
bool private autoReleaseFlag;
ISystemSettings public settings;
mapping(bytes32 => Payment) public payments;
bool public autoReleaseFlag;
bool public paused;

IPurchaseTracker public purchaseTracker;
Expand All @@ -40,7 +41,8 @@ contract PaymentEscrow is HasSecurityContext, IEscrowContract
event ReleaseAssentGiven (
bytes32 indexed paymentId,
address assentingAddress,
uint8 assentType // 1 = payer, 2 = receiver, 3 = arbiter
//TODO: consider making this an enum
uint8 assentType // 1 = payer, 2 = receiver, 3 = arbiter, 4 = admin
);

event EscrowReleased (
Expand Down Expand Up @@ -184,29 +186,28 @@ contract PaymentEscrow is HasSecurityContext, IEscrowContract

if (msg.sender != payment.receiver &&
msg.sender != payment.payer &&
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender))
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender) &&
!this.hasAdminPermission(payment.receiver, msg.sender, PermissionRelease))
{
revert("Unauthorized");
}

if (payment.amount > 0) {
if (payment.receiver == msg.sender) {
if (!payment.receiverReleased) {
payment.receiverReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 1);
}
if (!payment.receiverReleased && payment.receiver == msg.sender) {
payment.receiverReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 1);
}
if (payment.payer == msg.sender) {
if (!payment.payerReleased) {
payment.payerReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 2);
}
if (!payment.receiverReleased && this.hasAdminPermission(payment.receiver, msg.sender, PermissionRelease)) {
payment.receiverReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 4);
}
if (securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender)) {
if (!payment.payerReleased) {
payment.payerReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 3);
}
if (!payment.payerReleased && payment.payer == msg.sender) {
payment.payerReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 2);
}
if (!payment.payerReleased && securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender)) {
payment.payerReleased = true;
emit ReleaseAssentGiven(paymentId, msg.sender, 3);
}

_releaseEscrowPayment(paymentId);
Expand All @@ -233,10 +234,16 @@ contract PaymentEscrow is HasSecurityContext, IEscrowContract
function refundPayment(bytes32 paymentId, uint256 amount) external whenNotPaused {
Payment storage payment = payments[paymentId];
require(payment.released == false, "Payment already released");

if (payment.amount > 0 && payment.amountRefunded <= payment.amount) {
if (payment.receiver != msg.sender && !securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender))
//check permission to refund
if (payment.receiver != msg.sender &&
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender) &&
!this.hasAdminPermission(payment.receiver, msg.sender, PermissionRefund)
)
revert("Unauthorized");

//check amount to refund
uint256 activeAmount = payment.amount - payment.amountRefunded;
if (amount > activeAmount)
revert("AmountExceeded");
Expand Down
49 changes: 49 additions & 0 deletions src/PaymentEscrowAdmins.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "./security/HasSecurityContext.sol";
import "./security/Roles.sol";
import "./ISystemSettings.sol";
import "./CarefulMath.sol";
import "./PaymentInput.sol";
import "./IEscrowContract.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IPurchaseTracker.sol";
import "./PaymentEscrowAdmins.sol";


enum PaymentEscrowAdminPermission {
None,
Release,
Refund,
All
}

/**
* @title PaymentEscrowAdmins
*
* Add-on module for PaymentEscrow; allows PaymentEscrow to appoint admins who can, like the
* store owner, refund and release escrows.
*
* @author John R. Kosinski
* LoadPipe 2024
*/
contract PaymentEscrowAdmins
{
uint8 public constant PermissionRefund = 1; // 00000001
uint8 public constant PermissionRelease = 1 << 1; // 00000010

mapping(address => mapping(address => uint8)) public appointedAdmins;

function grantAdminPermission(address admin, uint8 permission) public {
appointedAdmins[msg.sender][admin] |= permission;
}

function revokeAdminPermission(address admin, uint8 permission) public {
appointedAdmins[msg.sender][admin] &= ~permission;
}

function hasAdminPermission(address owner, address admin, uint8 permission) public view returns (bool) {
return appointedAdmins[owner][admin] & permission != 0;
}
}
Loading
Loading