Skip to content

Commit 18a04d2

Browse files
authoredMar 10, 2025··
Merge pull request #13 from LoadPipe/HAMESC-27-appoint-admins
HAMESC-27: Appoint admin permissions to escrow
2 parents fc0e1f2 + ac68c61 commit 18a04d2

File tree

4 files changed

+374
-23
lines changed

4 files changed

+374
-23
lines changed
 

‎hardhat.config.ts

+25
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ const config: HardhatUserConfig = {
2525
chainId: 10,
2626
url: `https://mainnet.optimism.io`,
2727
},
28+
ethereum: {
29+
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
30+
chainId: 1,
31+
url: `https://ethereum-rpc.publicnode.com`,
32+
},
33+
arbitrum: {
34+
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
35+
chainId: 42161,
36+
url: `https://arb1.arbitrum.io/rpc`,
37+
},
38+
base: {
39+
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
40+
chainId: 8453,
41+
url: `https://mainnet.base.org`,
42+
},
43+
polygon: {
44+
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
45+
chainId: 137,
46+
url: `https://polygon.api.onfinality.io/public`,
47+
},
48+
amoy: {
49+
accounts: [process.env.OPTIMISM_PRIVATE_KEY ?? ''],
50+
chainId: 80002,
51+
url: `https://rpc-amoy.polygon.technology`,
52+
},
2853
op_sepolia: {
2954
url: 'https://sepolia.optimism.io',
3055
chainId: 11155420,

‎src/PaymentEscrow.sol

+29-22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "./PaymentInput.sol";
99
import "./IEscrowContract.sol";
1010
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1111
import "./IPurchaseTracker.sol";
12+
import "./PaymentEscrowAdmins.sol";
1213

1314
/**
1415
* @title PaymentEscrow
@@ -19,11 +20,11 @@ import "./IPurchaseTracker.sol";
1920
* @author John R. Kosinski
2021
* LoadPipe 2024
2122
*/
22-
contract PaymentEscrow is HasSecurityContext, IEscrowContract
23+
contract PaymentEscrow is PaymentEscrowAdmins, HasSecurityContext, IEscrowContract
2324
{
24-
ISystemSettings private settings;
25-
mapping(bytes32 => Payment) private payments;
26-
bool private autoReleaseFlag;
25+
ISystemSettings public settings;
26+
mapping(bytes32 => Payment) public payments;
27+
bool public autoReleaseFlag;
2728
bool public paused;
2829

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

4648
event EscrowReleased (
@@ -184,29 +186,28 @@ contract PaymentEscrow is HasSecurityContext, IEscrowContract
184186

185187
if (msg.sender != payment.receiver &&
186188
msg.sender != payment.payer &&
187-
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender))
189+
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender) &&
190+
!this.hasAdminPermission(payment.receiver, msg.sender, PermissionRelease))
188191
{
189192
revert("Unauthorized");
190193
}
191194

192195
if (payment.amount > 0) {
193-
if (payment.receiver == msg.sender) {
194-
if (!payment.receiverReleased) {
195-
payment.receiverReleased = true;
196-
emit ReleaseAssentGiven(paymentId, msg.sender, 1);
197-
}
196+
if (!payment.receiverReleased && payment.receiver == msg.sender) {
197+
payment.receiverReleased = true;
198+
emit ReleaseAssentGiven(paymentId, msg.sender, 1);
198199
}
199-
if (payment.payer == msg.sender) {
200-
if (!payment.payerReleased) {
201-
payment.payerReleased = true;
202-
emit ReleaseAssentGiven(paymentId, msg.sender, 2);
203-
}
200+
if (!payment.receiverReleased && this.hasAdminPermission(payment.receiver, msg.sender, PermissionRelease)) {
201+
payment.receiverReleased = true;
202+
emit ReleaseAssentGiven(paymentId, msg.sender, 4);
204203
}
205-
if (securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender)) {
206-
if (!payment.payerReleased) {
207-
payment.payerReleased = true;
208-
emit ReleaseAssentGiven(paymentId, msg.sender, 3);
209-
}
204+
if (!payment.payerReleased && payment.payer == msg.sender) {
205+
payment.payerReleased = true;
206+
emit ReleaseAssentGiven(paymentId, msg.sender, 2);
207+
}
208+
if (!payment.payerReleased && securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender)) {
209+
payment.payerReleased = true;
210+
emit ReleaseAssentGiven(paymentId, msg.sender, 3);
210211
}
211212

212213
_releaseEscrowPayment(paymentId);
@@ -233,10 +234,16 @@ contract PaymentEscrow is HasSecurityContext, IEscrowContract
233234
function refundPayment(bytes32 paymentId, uint256 amount) external whenNotPaused {
234235
Payment storage payment = payments[paymentId];
235236
require(payment.released == false, "Payment already released");
237+
236238
if (payment.amount > 0 && payment.amountRefunded <= payment.amount) {
237-
if (payment.receiver != msg.sender && !securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender))
239+
//check permission to refund
240+
if (payment.receiver != msg.sender &&
241+
!securityContext.hasRole(Roles.ARBITER_ROLE, msg.sender) &&
242+
!this.hasAdminPermission(payment.receiver, msg.sender, PermissionRefund)
243+
)
238244
revert("Unauthorized");
239245

246+
//check amount to refund
240247
uint256 activeAmount = payment.amount - payment.amountRefunded;
241248
if (amount > activeAmount)
242249
revert("AmountExceeded");

‎src/PaymentEscrowAdmins.sol

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import "./security/HasSecurityContext.sol";
5+
import "./security/Roles.sol";
6+
import "./ISystemSettings.sol";
7+
import "./CarefulMath.sol";
8+
import "./PaymentInput.sol";
9+
import "./IEscrowContract.sol";
10+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11+
import "./IPurchaseTracker.sol";
12+
import "./PaymentEscrowAdmins.sol";
13+
14+
15+
enum PaymentEscrowAdminPermission {
16+
None,
17+
Release,
18+
Refund,
19+
All
20+
}
21+
22+
/**
23+
* @title PaymentEscrowAdmins
24+
*
25+
* Add-on module for PaymentEscrow; allows PaymentEscrow to appoint admins who can, like the
26+
* store owner, refund and release escrows.
27+
*
28+
* @author John R. Kosinski
29+
* LoadPipe 2024
30+
*/
31+
contract PaymentEscrowAdmins
32+
{
33+
uint8 public constant PermissionRefund = 1; // 00000001
34+
uint8 public constant PermissionRelease = 1 << 1; // 00000010
35+
36+
mapping(address => mapping(address => uint8)) public appointedAdmins;
37+
38+
function grantAdminPermission(address admin, uint8 permission) public {
39+
appointedAdmins[msg.sender][admin] |= permission;
40+
}
41+
42+
function revokeAdminPermission(address admin, uint8 permission) public {
43+
appointedAdmins[msg.sender][admin] &= ~permission;
44+
}
45+
46+
function hasAdminPermission(address owner, address admin, uint8 permission) public view returns (bool) {
47+
return appointedAdmins[owner][admin] & permission != 0;
48+
}
49+
}

0 commit comments

Comments
 (0)
Please sign in to comment.