From fe2657e8ceae57e5da95545b171f0e907a3d7dc0 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Wed, 1 Jul 2026 15:00:41 +0300 Subject: [PATCH 1/4] add bundles & relocks --- contracts/escrow/EnterpriseEscrow.sol | 230 +++++++++++++++++++--- contracts/escrow/Escrow.sol | 220 ++++++++++++++++++--- test/unit/escrow/EnterpriseEscrow.test.js | 210 ++++++++++++++++++++ test/unit/escrow/Escrow.test.js | 188 ++++++++++++++++++ 4 files changed, 801 insertions(+), 47 deletions(-) diff --git a/contracts/escrow/EnterpriseEscrow.sol b/contracts/escrow/EnterpriseEscrow.sol index f56ff335..f66c6f32 100644 --- a/contracts/escrow/EnterpriseEscrow.sol +++ b/contracts/escrow/EnterpriseEscrow.sol @@ -20,13 +20,19 @@ import "../interfaces/IEnterpriseFeeCollector.sol"; * The payer flow looks like: * - payer deposits token * - payer sets limit for payees (max amount, max process time) - * - * + * - payer can bundle multiple deposits (plain or via ERC20Permit) and authorizations + * in a single transaction using bundle() + * + * * The payee flow looks like: * - payer asks for service (like compute) offchain * - payee computes the maximum amount and locks that amount in the escrow contract * - payee performs the service * - payee takes the actual amount from the lock and releases back the remaining + * - while a lock is still active, the payee can reLock() it to change the amount and/or + * expiry (up or down). Every createLock check is re-evaluated against the new values, and + * the total lock lifetime (measured from the original creation) cannot exceed the + * authorization's maxLockSeconds. */ contract EnterpriseEscrow is ReentrancyGuard @@ -68,18 +74,31 @@ contract EnterpriseEscrow is uint256 jobId; address payer; uint256 amount; - uint256 expiry; + uint256 expiry; // absolute timestamp: block.timestamp + duration address token; + uint256 startTime; // block.timestamp at original createLock; preserved across reLock } mapping(address => lock[]) private locks; // locks by payee + /* structs used to bundle multiple payer actions in a single call */ + struct DepositData { address token; uint256 amount; } + struct PermitData { address token; uint256 amount; uint256 deadline; uint8 v; bytes32 r; bytes32 s; } + struct AuthData { address token; address payee; uint256 maxLockedAmount; uint256 maxLockSeconds; uint256 maxLockCounts; } + + /* structs used to bundle multiple payee (job) actions in a single call */ + struct LockData { uint256 jobId; address token; address payer; uint256 amount; uint256 expiry; } // createLock & reLock + struct ClaimData { uint256 jobId; address token; address payer; uint256 amount; bytes proof; } + struct CancelData { uint256 jobId; address token; address payer; address payee; } + // events event Deposit(address indexed payer,address token,uint256 amount); event Withdraw(address indexed payer,address token,uint256 amount); - event Auth(address indexed payer,address indexed payee,uint256 maxLockedAmount, + event Auth(address indexed payer,address indexed payee,address token,uint256 maxLockedAmount, uint256 maxLockSeconds,uint256 maxLockCounts); event Lock(address payer,address payee,uint256 jobId,uint256 amount,uint256 expiry,address token); + event ReLock(address payer,address payee,uint256 jobId,uint256 oldAmount,uint256 newAmount, + uint256 newExpiry,address token); event Claimed(address indexed payee,uint256 jobId,address token,address indexed payer,uint256 amount,bytes proof); event Canceled(address indexed payee,uint256 jobId,address token,address indexed payer,uint256 amount); @@ -106,7 +125,7 @@ contract EnterpriseEscrow is * @param token array of tokens to deposit * @param amount array of amounts in wei to deposit */ - function depositMultiple(address[] memory token,uint256[] memory amount) external nonReentrant{ + function depositMultiple(address[] calldata token,uint256[] calldata amount) external nonReentrant{ require(token.length==amount.length,"Invalid input"); for(uint256 i=0;i cancels -> creates -> reLocks, so lock slots / locked amounts freed by claims and + * cancels are available before new locks are created. Any sub-array may be empty. + * + * @param claims array of {jobId, token, payer, amount, proof} passed to claimLock + * @param cancels array of {jobId, token, payer, payee} passed to cancelExpiredLock + * @param newLocks array of {jobId, token, payer, amount, expiry} passed to createLock + * @param reLockOps array of {jobId, token, payer, amount, expiry} passed to reLock + */ + function bundleJobs( + ClaimData[] calldata claims, + CancelData[] calldata cancels, + LockData[] calldata newLocks, + LockData[] calldata reLockOps + ) external nonReentrant { + for(uint256 i=0;i0,"Invalid amount"); + require(jobId>0,"Invalid jobId"); + // find the existing lock + lock memory tempLock=lock(0,address(0),0,0,address(0),0); + uint256 lockIndex; + uint256 length=locks[msg.sender].length; + for(lockIndex=0;lockIndex=block.timestamp,"Lock expired"); + // find the auth + auth memory tempAuth=auth(address(0),0,0,0,0,0); + uint256 index; + length=userAuths[payer][token].length; + for(index=0;index=amount,"Payer does not have enough funds"); + require(block.timestamp+expiry<=tempLock.startTime+tempAuth.maxLockSeconds,"Expiry too high"); + require(tempAuth.currentLockedAmount-tempLock.amount+amount<=tempAuth.maxLockedAmount,"Exceeds maxLockedAmount"); + // check enterprise fee with the new amount + if(opcCollector != address(0)){ + require( + IEnterpriseFeeCollector(opcCollector).isTokenAllowed(token), + "This token is not allowed by enterprise fee collector" + ); + uint256 enterpriseFee = IEnterpriseFeeCollector(opcCollector).calculateFee(token,amount); + require( + enterpriseFee0,'Invalid jobId'); - lock memory tempLock=lock(0,address(0),0,0,address(0)); + lock memory tempLock=lock(0,address(0),0,0,address(0),0); uint256 index; uint256 length=locks[msg.sender].length; for(index=0;index lock[]) private locks; // locks by payee - + + /* structs used to bundle multiple payer actions in a single call */ + struct DepositData { address token; uint256 amount; } + struct PermitData { address token; uint256 amount; uint256 deadline; uint8 v; bytes32 r; bytes32 s; } + struct AuthData { address token; address payee; uint256 maxLockedAmount; uint256 maxLockSeconds; uint256 maxLockCounts; } + + /* structs used to bundle multiple payee (job) actions in a single call */ + struct LockData { uint256 jobId; address token; address payer; uint256 amount; uint256 expiry; } // createLock & reLock + struct ClaimData { uint256 jobId; address token; address payer; uint256 amount; bytes proof; } + struct CancelData { uint256 jobId; address token; address payer; address payee; } + // events event Deposit(address indexed payer,address token,uint256 amount); event Withdraw(address indexed payer,address token,uint256 amount); - event Auth(address indexed payer,address indexed payee,uint256 maxLockedAmount, + event Auth(address indexed payer,address indexed payee,address token,uint256 maxLockedAmount, uint256 maxLockSeconds,uint256 maxLockCounts); event Lock(address payer,address payee,uint256 jobId,uint256 amount,uint256 expiry,address token); + event ReLock(address payer,address payee,uint256 jobId,uint256 oldAmount,uint256 newAmount, + uint256 newExpiry,address token); event Claimed(address indexed payee,uint256 jobId,address token,address indexed payer,uint256 amount,bytes proof); event Canceled(address indexed payee,uint256 jobId,address token,address indexed payer,uint256 amount); @@ -110,7 +129,7 @@ contract Escrow is * @param token array of tokens to deposit * @param amount array of amounts in wei to deposit */ - function depositMultiple(address[] memory token,uint256[] memory amount) external nonReentrant{ + function depositMultiple(address[] calldata token,uint256[] calldata amount) external nonReentrant{ require(token.length==amount.length,"Invalid input"); for(uint256 i=0;i cancels -> creates -> reLocks, so lock slots / locked amounts freed by claims and + * cancels are available before new locks are created. Any sub-array may be empty. + * + * @param claims array of {jobId, token, payer, amount, proof} passed to claimLock + * @param cancels array of {jobId, token, payer, payee} passed to cancelExpiredLock + * @param newLocks array of {jobId, token, payer, amount, expiry} passed to createLock + * @param reLockOps array of {jobId, token, payer, amount, expiry} passed to reLock + */ + function bundleJobs( + ClaimData[] calldata claims, + CancelData[] calldata cancels, + LockData[] calldata newLocks, + LockData[] calldata reLockOps + ) external nonReentrant { + for(uint256 i=0;i0,"Invalid amount"); + require(jobId>0,"Invalid jobId"); + // find the existing lock + lock memory tempLock=lock(0,address(0),0,0,address(0),0); + uint256 lockIndex; + uint256 length=locks[msg.sender].length; + for(lockIndex=0;lockIndex=block.timestamp,"Lock expired"); + // find the auth + auth memory tempAuth=auth(address(0),0,0,0,0,0); + uint256 index; + length=userAuths[payer][token].length; + for(index=0;index=amount,"Payer does not have enough funds"); + require(block.timestamp+expiry<=tempLock.startTime+tempAuth.maxLockSeconds,"Expiry too high"); + require(tempAuth.currentLockedAmount-tempLock.amount+amount<=tempAuth.maxLockedAmount,"Exceeds maxLockedAmount"); + // update auths (currentLocks unchanged - same slot) + userAuths[payer][token][index].currentLockedAmount=tempAuth.currentLockedAmount-tempLock.amount+amount; + // update user funds + funds[payer][token].available=funds[payer][token].available+tempLock.amount-amount; + funds[payer][token].locked=funds[payer][token].locked-tempLock.amount+amount; + // update the lock in place (jobId and startTime preserved) + locks[msg.sender][lockIndex].amount=amount; + locks[msg.sender][lockIndex].expiry=block.timestamp+expiry; + emit ReLock(payer,msg.sender,jobId,tempLock.amount,amount,expiry,token); + } + /** * @dev claimLock * Called by payee to claim a lock (fully or partial) @@ -438,7 +610,7 @@ contract Escrow is * @param amount amount in wei to claim * @param proof job proof */ - function claimLock(uint256 jobId,address token,address payer,uint256 amount,bytes memory proof) + function claimLock(uint256 jobId,address token,address payer,uint256 amount,bytes calldata proof) external nonReentrant{ _claimLock(jobId,token,payer,amount,proof); } @@ -454,9 +626,9 @@ contract Escrow is * @param amount array amounts in wei to claim * @param proof array of job proofs */ - function claimLocks(uint256[] memory jobId,address[] memory token, - address[] memory payer,uint256[] memory amount, - bytes[] memory proof) external nonReentrant{ + function claimLocks(uint256[] calldata jobId,address[] calldata token, + address[] calldata payer,uint256[] calldata amount, + bytes[] calldata proof) external nonReentrant{ require(jobId.length==token.length && jobId.length==payer.length && @@ -478,7 +650,7 @@ contract Escrow is * @param proof job proof */ function claimLockAndWithdraw(uint256 jobId,address token,address payer, - uint256 amount,bytes memory proof) external nonReentrant{ + uint256 amount,bytes calldata proof) external nonReentrant{ _claimLock(jobId,token,payer,amount,proof); _withdraw(token,funds[msg.sender][token].available); @@ -494,8 +666,8 @@ contract Escrow is * @param amount array amounts in wei to claim * @param proof array of job proofs */ - function claimLocksAndWithdraw(uint256[] memory jobId,address[] memory token, - address[] memory payer,uint256[] memory amount,bytes[] memory proof) external nonReentrant{ + function claimLocksAndWithdraw(uint256[] calldata jobId,address[] calldata token, + address[] calldata payer,uint256[] calldata amount,bytes[] calldata proof) external nonReentrant{ require(jobId.length==token.length && jobId.length==payer.length && @@ -513,11 +685,11 @@ contract Escrow is } function _claimLock(uint256 jobId,address token,address payer,uint256 amount, - bytes memory proof) internal { + bytes calldata proof) internal { require(payer!=address(0),'Invalid payer'); require(token!=address(0),'Invalid token'); require(jobId>0,'Invalid jobId'); - lock memory tempLock=lock(0,address(0),0,0,address(0)); + lock memory tempLock=lock(0,address(0),0,0,address(0),0); uint256 index; uint256 length=locks[msg.sender].length; for(index=0;index (await EscrowContract.connect(payee3).getLocks(Mock20Contract.address, payer3.address, payee3.address)).find(l => l.jobId.eq(jobId)); + const created = await findLock(); + const startTime = created.startTime; + const base = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); // after createLock(10) + + // reLock UP to 25 + const tx = await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("25"), 600); + const ev = getEventFromTx(await tx.wait(), 'ReLock'); + assert(ev, "Cannot find ReLock event"); + expect(ev.args.oldAmount).to.equal(web3.utils.toWei("10")); + expect(ev.args.newAmount).to.equal(web3.utils.toWei("25")); + expect(ev.args.token).to.equal(Mock20Contract.address); + let funds = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); + expect(funds.locked).to.equal(base.locked.add(web3.utils.toWei("15"))); + expect(funds.available).to.equal(base.available.sub(web3.utils.toWei("15"))); + let lk = await findLock(); + expect(lk.amount).to.equal(web3.utils.toWei("25")); + expect(lk.startTime).to.equal(startTime); // preserved across reLock + let au = (await EscrowContract.connect(payer3).getAuthorizations(Mock20Contract.address, payer3.address, payee3.address))[0]; + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("25")); + expect(au.currentLocks).to.equal(1); + + // reLock DOWN to 5 + await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("5"), 200); + funds = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); + expect(funds.locked).to.equal(base.locked.sub(web3.utils.toWei("5"))); + expect(funds.available).to.equal(base.available.add(web3.utils.toWei("5"))); + lk = await findLock(); + expect(lk.amount).to.equal(web3.utils.toWei("5")); + expect(lk.startTime).to.equal(startTime); + au = (await EscrowContract.connect(payer3).getAuthorizations(Mock20Contract.address, payer3.address, payee3.address))[0]; + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("5")); + expect(au.currentLocks).to.equal(1); // unchanged + }); + + it('Escrow - reLock caps total lifetime at maxLockSeconds from original start', async function () { + const jobId = 1002; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + const lk = (await EscrowContract.connect(payee3).getLocks(Mock20Contract.address, payer3.address, payee3.address)).find(l => l.jobId.eq(jobId)); + await fastForward(200); + const cap = lk.startTime.toNumber() + 1000; // maxLockSeconds = 1000 + const now = await blocktimestamp(); + const okExpiry = cap - now - 5; + const badExpiry = cap - now + 50; + // extending within the cap succeeds + await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), okExpiry); + // extending beyond startTime + maxLockSeconds reverts + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), badExpiry) + ).to.be.revertedWith("Expiry too high"); + }); + + it('Escrow - reLock reverts (not found / funds / maxLocked / expired)', async function () { + const jobId = 1003; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + // wrong jobId + await expect( + EscrowContract.connect(payee3).reLock(999999, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 100) + ).to.be.revertedWith("Lock not found"); + // amount beyond available + old -> not enough funds + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("100000"), 100) + ).to.be.revertedWith("Payer does not have enough funds"); + // amount within funds but beyond maxLockedAmount (1000) + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("1500"), 100) + ).to.be.revertedWith("Exceeds maxLockedAmount"); + // expired lock cannot be reLocked + const expiringJob = 1004; + await EscrowContract.connect(payee3).createLock(expiringJob, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 50); + await fastForward(100); + await expect( + EscrowContract.connect(payee3).reLock(expiringJob, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 40) + ).to.be.revertedWith("Lock expired"); + }); + + it('Escrow - reLock re-checks the enterprise fee gate', async function () { + // Mock20Contract minFee = 1 wei, so calculateFee(amount=1) returns 1 >= amount -> reverts + const jobId = 2001; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, 1, 100) + ).to.be.revertedWith("Amount must be higher than enterprise fee"); + // a normal reLock still settles + await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("12"), 100); + const lk = (await EscrowContract.connect(payee3).getLocks(Mock20Contract.address, payer3.address, payee3.address)).find(l => l.jobId.eq(jobId)); + expect(lk.amount).to.equal(web3.utils.toWei("12")); + }); + + // ---------- bundleJobs: batch payee-side operations ---------- + + it('Escrow - bundleJobs runs claims before creates so freed capacity is reusable', async function () { + // dedicated tight auth: payer2 -> payee2, maxLockCounts = 2 + await Mock20Contract.connect(payer2).approve(EscrowContract.address, web3.utils.toWei("10000")); + await EscrowContract.connect(payer2).deposit(Mock20Contract.address, web3.utils.toWei("200")); + await EscrowContract.connect(payer2).authorize(Mock20Contract.address, payee2.address, web3.utils.toWei("100"), 1000, 2); + // fill capacity with 2 locks + await EscrowContract.connect(payee2).createLock(5001, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500); + await EscrowContract.connect(payee2).createLock(5002, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500); + // at capacity: a plain 3rd createLock reverts + await expect( + EscrowContract.connect(payee2).createLock(5003, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500) + ).to.be.revertedWith("Exceeds maxLockCounts"); + // but bundleJobs claims 5001 first (frees a slot), then creates 5003 and reLocks 5002 -> all atomic + const claims = [{ jobId: 5001, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("10"), proof: "0x" }]; + const cancels = []; + const newLocks = [{ jobId: 5003, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("10"), expiry: 500 }]; + const reLocks = [{ jobId: 5002, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("15"), expiry: 400 }]; + const rc = await (await EscrowContract.connect(payee2).bundleJobs(claims, cancels, newLocks, reLocks)).wait(); + assert(getEventFromTx(rc, 'Claimed'), "missing Claimed event"); + assert(getEventFromTx(rc, 'Lock'), "missing Lock event"); + assert(getEventFromTx(rc, 'ReLock'), "missing ReLock event"); + const locks = await EscrowContract.connect(payee2).getLocks(Mock20Contract.address, payer2.address, payee2.address); + expect(locks.find(l => l.jobId.eq(5001))).to.be.undefined; // claimed + expect(locks.find(l => l.jobId.eq(5002)).amount).to.equal(web3.utils.toWei("15")); // reLocked + expect(locks.find(l => l.jobId.eq(5003)).amount).to.equal(web3.utils.toWei("10")); // created + const au = (await EscrowContract.connect(payer2).getAuthorizations(Mock20Contract.address, payer2.address, payee2.address))[0]; + expect(au.currentLocks).to.equal(2); // 5002 + 5003 (5001 claimed) + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("25")); // 15 + 10 + }); + + it('Escrow - bundleJobs cancels expired locks', async function () { + // raise the count cap, create a short-lived lock, let it expire + await EscrowContract.connect(payer2).authorize(Mock20Contract.address, payee2.address, web3.utils.toWei("1000"), 1000, 10); + await EscrowContract.connect(payee2).createLock(5101, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 50); + await fastForward(100); + const before = await EscrowContract.connect(payer2).getFunds(Mock20Contract.address); + const rc = await (await EscrowContract.connect(payee2).bundleJobs( + [], [{ jobId: 5101, token: Mock20Contract.address, payer: payer2.address, payee: payee2.address }], [], [] + )).wait(); + assert(getEventFromTx(rc, 'Canceled'), "missing Canceled event"); + const locks = await EscrowContract.connect(payee2).getLocks(Mock20Contract.address, payer2.address, payee2.address); + expect(locks.find(l => l.jobId.eq(5101))).to.be.undefined; + const after = await EscrowContract.connect(payer2).getFunds(Mock20Contract.address); + expect(after.locked).to.equal(before.locked.sub(web3.utils.toWei("10"))); + expect(after.available).to.equal(before.available.add(web3.utils.toWei("10"))); + }); + + it('Escrow - bundleJobs create still enforces the enterprise fee gate', async function () { + // amount=1 wei -> calculateFee returns minFee(1) >= amount -> reverts inside _createLock + await expect( + EscrowContract.connect(payee2).bundleJobs( + [], [], [{ jobId: 5201, token: Mock20Contract.address, payer: payer2.address, amount: 1, expiry: 100 }], [] + ) + ).to.be.revertedWith("Amount must be higher than enterprise fee"); + }); + + it('Escrow - bundleJobs with all-empty arrays is a no-op', async function () { + await EscrowContract.connect(payee2).bundleJobs([], [], [], []); + }); }); \ No newline at end of file diff --git a/test/unit/escrow/Escrow.test.js b/test/unit/escrow/Escrow.test.js index 7c9dc6ff..a457e41c 100644 --- a/test/unit/escrow/Escrow.test.js +++ b/test/unit/escrow/Escrow.test.js @@ -525,4 +525,192 @@ it('Escrow - lock', async function () { const funds = await EscrowContract.connect(payer3).getFunds(Mock20PermitContract.address); expect(funds.available).to.equal(depositAmount); }); + + // ---------- new feature tests: Auth token, bundle, reLock ---------- + + it('Escrow - Auth event includes token', async function () { + const tx = await EscrowContract.connect(payer3).authorize( + Mock20Contract.address, payee3.address, web3.utils.toWei("1000"), 1000, 10 + ); + const event = getEventFromTx(await tx.wait(), 'Auth'); + assert(event, "Cannot find Auth event"); + expect(event.args.payer).to.equal(payer3.address); + expect(event.args.payee).to.equal(payee3.address); + expect(event.args.token).to.equal(Mock20Contract.address); + expect(event.args.maxLockedAmount).to.equal(web3.utils.toWei("1000")); + expect(event.args.maxLockSeconds).to.equal(1000); + expect(event.args.maxLockCounts).to.equal(10); + }); + + it('Escrow - bundle deposits, permit-deposit and auths in one call', async function () { + const depAmount = web3.utils.toWei("20"); + const permitAmount = web3.utils.toWei("30"); + await Mock20Contract.connect(payer2).approve(EscrowContract.address, web3.utils.toWei("10000")); + const beforeMock20 = (await EscrowContract.connect(payer2).getFunds(Mock20Contract.address)).available; + const beforePermit = (await EscrowContract.connect(payer2).getFunds(Mock20PermitContract.address)).available; + const block = await ethers.provider.getBlock("latest"); + const deadline = block.timestamp + 3600; + const nonce = await Mock20PermitContract.nonces(payer2.address); + const { v, r, s } = await signPermit(payer2, Mock20PermitContract, EscrowContract.address, permitAmount, deadline, nonce); + const deposits = [{ token: Mock20Contract.address, amount: depAmount }]; + const permits = [{ token: Mock20PermitContract.address, amount: permitAmount, deadline, v, r, s }]; + const auths = [ + { token: Mock20Contract.address, payee: payee2.address, maxLockedAmount: web3.utils.toWei("40"), maxLockSeconds: 500, maxLockCounts: 3 }, + { token: Mock20PermitContract.address, payee: payee3.address, maxLockedAmount: web3.utils.toWei("10"), maxLockSeconds: 200, maxLockCounts: 1 }, + ]; + await EscrowContract.connect(payer2).bundle(deposits, permits, auths); + expect((await EscrowContract.connect(payer2).getFunds(Mock20Contract.address)).available).to.equal(beforeMock20.add(depAmount)); + expect((await EscrowContract.connect(payer2).getFunds(Mock20PermitContract.address)).available).to.equal(beforePermit.add(permitAmount)); + expect(await EscrowContract.connect(payer2).getUserTokens(payer2.address)).to.include(Mock20PermitContract.address); + const a1 = await EscrowContract.connect(payer2).getAuthorizations(Mock20Contract.address, payer2.address, payee2.address); + expect(a1.length).to.equal(1); + expect(a1[0].maxLockedAmount).to.equal(web3.utils.toWei("40")); + const a2 = await EscrowContract.connect(payer2).getAuthorizations(Mock20PermitContract.address, payer2.address, payee3.address); + expect(a2.length).to.equal(1); + expect(a2[0].maxLockSeconds).to.equal(200); + }); + + it('Escrow - bundle works with empty sub-arrays (auths only)', async function () { + await EscrowContract.connect(payer2).bundle([], [], [ + { token: Mock20Contract.address, payee: payee3.address, maxLockedAmount: web3.utils.toWei("5"), maxLockSeconds: 100, maxLockCounts: 1 }, + ]); + const a = await EscrowContract.connect(payer2).getAuthorizations(Mock20Contract.address, payer2.address, payee3.address); + expect(a.length).to.equal(1); + expect(a[0].maxLockedAmount).to.equal(web3.utils.toWei("5")); + }); + + it('Escrow - reLock increases and decreases the amount', async function () { + // fund payer3 and lock as payee3 (auth set in the Auth-event test: 1000 max, 1000s, 10 counts) + await Mock20Contract.connect(payer3).approve(EscrowContract.address, web3.utils.toWei("10000")); + await EscrowContract.connect(payer3).deposit(Mock20Contract.address, web3.utils.toWei("2000")); + const jobId = 1001; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + const findLock = async () => (await EscrowContract.connect(payee3).getLocks(Mock20Contract.address, payer3.address, payee3.address)).find(l => l.jobId.eq(jobId)); + const created = await findLock(); + const startTime = created.startTime; + const base = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); // after createLock(10) + + // reLock UP to 25 + const tx = await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("25"), 600); + const ev = getEventFromTx(await tx.wait(), 'ReLock'); + assert(ev, "Cannot find ReLock event"); + expect(ev.args.oldAmount).to.equal(web3.utils.toWei("10")); + expect(ev.args.newAmount).to.equal(web3.utils.toWei("25")); + expect(ev.args.token).to.equal(Mock20Contract.address); + let funds = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); + expect(funds.locked).to.equal(base.locked.add(web3.utils.toWei("15"))); + expect(funds.available).to.equal(base.available.sub(web3.utils.toWei("15"))); + let lk = await findLock(); + expect(lk.amount).to.equal(web3.utils.toWei("25")); + expect(lk.startTime).to.equal(startTime); // preserved across reLock + let au = (await EscrowContract.connect(payer3).getAuthorizations(Mock20Contract.address, payer3.address, payee3.address))[0]; + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("25")); + expect(au.currentLocks).to.equal(1); + + // reLock DOWN to 5 + await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("5"), 200); + funds = await EscrowContract.connect(payer3).getFunds(Mock20Contract.address); + expect(funds.locked).to.equal(base.locked.sub(web3.utils.toWei("5"))); + expect(funds.available).to.equal(base.available.add(web3.utils.toWei("5"))); + lk = await findLock(); + expect(lk.amount).to.equal(web3.utils.toWei("5")); + expect(lk.startTime).to.equal(startTime); + au = (await EscrowContract.connect(payer3).getAuthorizations(Mock20Contract.address, payer3.address, payee3.address))[0]; + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("5")); + expect(au.currentLocks).to.equal(1); // unchanged + }); + + it('Escrow - reLock caps total lifetime at maxLockSeconds from original start', async function () { + const jobId = 1002; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + const lk = (await EscrowContract.connect(payee3).getLocks(Mock20Contract.address, payer3.address, payee3.address)).find(l => l.jobId.eq(jobId)); + await fastForward(200); + const cap = lk.startTime.toNumber() + 1000; // maxLockSeconds = 1000 + const now = await blocktimestamp(); + const okExpiry = cap - now - 5; + const badExpiry = cap - now + 50; + // extending within the cap succeeds + await EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), okExpiry); + // extending beyond startTime + maxLockSeconds reverts + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), badExpiry) + ).to.be.revertedWith("Expiry too high"); + }); + + it('Escrow - reLock reverts (not found / funds / maxLocked / expired)', async function () { + const jobId = 1003; + await EscrowContract.connect(payee3).createLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 500); + // wrong jobId + await expect( + EscrowContract.connect(payee3).reLock(999999, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 100) + ).to.be.revertedWith("Lock not found"); + // amount beyond available + old -> not enough funds + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("100000"), 100) + ).to.be.revertedWith("Payer does not have enough funds"); + // amount within funds but beyond maxLockedAmount (1000) + await expect( + EscrowContract.connect(payee3).reLock(jobId, Mock20Contract.address, payer3.address, web3.utils.toWei("1500"), 100) + ).to.be.revertedWith("Exceeds maxLockedAmount"); + // expired lock cannot be reLocked + const expiringJob = 1004; + await EscrowContract.connect(payee3).createLock(expiringJob, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 50); + await fastForward(100); + await expect( + EscrowContract.connect(payee3).reLock(expiringJob, Mock20Contract.address, payer3.address, web3.utils.toWei("10"), 40) + ).to.be.revertedWith("Lock expired"); + }); + + // ---------- bundleJobs: batch payee-side operations ---------- + + it('Escrow - bundleJobs runs claims before creates so freed capacity is reusable', async function () { + // dedicated tight auth: payer2 -> payee2, maxLockCounts = 2 + await Mock20Contract.connect(payer2).approve(EscrowContract.address, web3.utils.toWei("10000")); + await EscrowContract.connect(payer2).deposit(Mock20Contract.address, web3.utils.toWei("200")); + await EscrowContract.connect(payer2).authorize(Mock20Contract.address, payee2.address, web3.utils.toWei("100"), 1000, 2); + // fill capacity with 2 locks + await EscrowContract.connect(payee2).createLock(5001, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500); + await EscrowContract.connect(payee2).createLock(5002, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500); + // at capacity: a plain 3rd createLock reverts + await expect( + EscrowContract.connect(payee2).createLock(5003, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 500) + ).to.be.revertedWith("Exceeds maxLockCounts"); + // but bundleJobs claims 5001 first (frees a slot), then creates 5003 and reLocks 5002 -> all atomic + const claims = [{ jobId: 5001, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("10"), proof: "0x" }]; + const cancels = []; + const newLocks = [{ jobId: 5003, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("10"), expiry: 500 }]; + const reLocks = [{ jobId: 5002, token: Mock20Contract.address, payer: payer2.address, amount: web3.utils.toWei("15"), expiry: 400 }]; + const rc = await (await EscrowContract.connect(payee2).bundleJobs(claims, cancels, newLocks, reLocks)).wait(); + assert(getEventFromTx(rc, 'Claimed'), "missing Claimed event"); + assert(getEventFromTx(rc, 'Lock'), "missing Lock event"); + assert(getEventFromTx(rc, 'ReLock'), "missing ReLock event"); + const locks = await EscrowContract.connect(payee2).getLocks(Mock20Contract.address, payer2.address, payee2.address); + expect(locks.find(l => l.jobId.eq(5001))).to.be.undefined; // claimed + expect(locks.find(l => l.jobId.eq(5002)).amount).to.equal(web3.utils.toWei("15")); // reLocked + expect(locks.find(l => l.jobId.eq(5003)).amount).to.equal(web3.utils.toWei("10")); // created + const au = (await EscrowContract.connect(payer2).getAuthorizations(Mock20Contract.address, payer2.address, payee2.address))[0]; + expect(au.currentLocks).to.equal(2); // 5002 + 5003 (5001 claimed) + expect(au.currentLockedAmount).to.equal(web3.utils.toWei("25")); // 15 + 10 + }); + + it('Escrow - bundleJobs cancels expired locks', async function () { + // raise the count cap, create a short-lived lock, let it expire + await EscrowContract.connect(payer2).authorize(Mock20Contract.address, payee2.address, web3.utils.toWei("1000"), 1000, 10); + await EscrowContract.connect(payee2).createLock(5101, Mock20Contract.address, payer2.address, web3.utils.toWei("10"), 50); + await fastForward(100); + const before = await EscrowContract.connect(payer2).getFunds(Mock20Contract.address); + const rc = await (await EscrowContract.connect(payee2).bundleJobs( + [], [{ jobId: 5101, token: Mock20Contract.address, payer: payer2.address, payee: payee2.address }], [], [] + )).wait(); + assert(getEventFromTx(rc, 'Canceled'), "missing Canceled event"); + const locks = await EscrowContract.connect(payee2).getLocks(Mock20Contract.address, payer2.address, payee2.address); + expect(locks.find(l => l.jobId.eq(5101))).to.be.undefined; + const after = await EscrowContract.connect(payer2).getFunds(Mock20Contract.address); + expect(after.locked).to.equal(before.locked.sub(web3.utils.toWei("10"))); + expect(after.available).to.equal(before.available.add(web3.utils.toWei("10"))); + }); + + it('Escrow - bundleJobs with all-empty arrays is a no-op', async function () { + await EscrowContract.connect(payee2).bundleJobs([], [], [], []); + }); }); \ No newline at end of file From b3e0129de76544e29a231fe06d14b32bd6f06af0 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Wed, 1 Jul 2026 16:00:17 +0300 Subject: [PATCH 2/4] fix slither --- contracts/escrow/EnterpriseEscrow.sol | 17 +++++++++++++++-- contracts/escrow/Escrow.sol | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/contracts/escrow/EnterpriseEscrow.sol b/contracts/escrow/EnterpriseEscrow.sol index f66c6f32..b6c27c30 100644 --- a/contracts/escrow/EnterpriseEscrow.sol +++ b/contracts/escrow/EnterpriseEscrow.sol @@ -186,6 +186,9 @@ contract EnterpriseEscrow is * @param permits array of {token, amount, deadline, v, r, s} for ERC20Permit-based deposits * @param auths array of {token, payee, maxLockedAmount, maxLockSeconds, maxLockCounts} */ + // Reentrancy-safe: nonReentrant blocks re-entry into every state-changing entrypoint; only view + // getters (the caller's own balances, not an oracle) are reachable during a token transfer/permit. + // slither-disable-next-line reentrancy-eth,reentrancy-benign function bundle( DepositData[] calldata deposits, PermitData[] calldata permits, @@ -214,6 +217,9 @@ contract EnterpriseEscrow is * @param newLocks array of {jobId, token, payer, amount, expiry} passed to createLock * @param reLockOps array of {jobId, token, payer, amount, expiry} passed to reLock */ + // Reentrancy-safe: nonReentrant blocks re-entry into every state-changing entrypoint; only view + // getters (the caller's own balances, not an oracle) are reachable during the claim fee transfer. + // slither-disable-next-line reentrancy-eth function bundleJobs( ClaimData[] calldata claims, CancelData[] calldata cancels, @@ -474,7 +480,7 @@ contract EnterpriseEscrow is } function _createLock(uint256 jobId,address token,address payer,uint256 amount,uint256 expiry) internal { require(payer!=address(0),'Invalid payer'); - require(payer!=msg.sender,'Payeer cannot be payee'); + require(payer!=msg.sender,'Payer cannot be payee'); require(token!=address(0),'Invalid token'); require(amount>0,"Invalid amount"); require(jobId>0,"Invalid jobId"); @@ -561,7 +567,7 @@ contract EnterpriseEscrow is } function _reLock(uint256 jobId,address token,address payer,uint256 amount,uint256 expiry) internal { require(payer!=address(0),'Invalid payer'); - require(payer!=msg.sender,'Payeer cannot be payee'); + require(payer!=msg.sender,'Payer cannot be payee'); require(token!=address(0),'Invalid token'); require(amount>0,"Invalid amount"); require(jobId>0,"Invalid jobId"); @@ -668,6 +674,8 @@ contract EnterpriseEscrow is * @param amount amount in wei to claim * @param proof job proof */ + // Reentrancy-safe: nonReentrant blocks re-entry; only view getters are reachable during the transfers. + // slither-disable-next-line reentrancy-eth function claimLockAndWithdraw(uint256 jobId,address token,address payer, uint256 amount,bytes calldata proof) external nonReentrant{ _claimLock(jobId,token,payer,amount,proof); @@ -685,6 +693,8 @@ contract EnterpriseEscrow is * @param amount array amounts in wei to claim * @param proof array of job proofs */ + // Reentrancy-safe: nonReentrant blocks re-entry; only view getters are reachable during the transfers. + // slither-disable-next-line reentrancy-eth function claimLocksAndWithdraw(uint256[] calldata jobId,address[] calldata token, address[] calldata payer,uint256[] calldata amount,bytes[] calldata proof) external nonReentrant{ @@ -703,6 +713,9 @@ contract EnterpriseEscrow is } } + // Reentrancy-safe: reached only via nonReentrant entrypoints, so re-entry is impossible; the + // funds/userTokens writes after the fee transfer cannot be exploited. + // slither-disable-next-line reentrancy-no-eth,reentrancy-benign function _claimLock(uint256 jobId,address token,address payer,uint256 amount, bytes calldata proof) internal { require(payer!=address(0),'Invalid payer'); diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index c14df514..96a3ef89 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -190,6 +190,9 @@ contract Escrow is * @param permits array of {token, amount, deadline, v, r, s} for ERC20Permit-based deposits * @param auths array of {token, payee, maxLockedAmount, maxLockSeconds, maxLockCounts} */ + // Reentrancy-safe: nonReentrant blocks re-entry into every state-changing entrypoint; only view + // getters (the caller's own balances, not an oracle) are reachable during a token transfer/permit. + // slither-disable-next-line reentrancy-eth,reentrancy-benign function bundle( DepositData[] calldata deposits, PermitData[] calldata permits, @@ -218,6 +221,9 @@ contract Escrow is * @param newLocks array of {jobId, token, payer, amount, expiry} passed to createLock * @param reLockOps array of {jobId, token, payer, amount, expiry} passed to reLock */ + // Reentrancy-safe: nonReentrant blocks re-entry into every state-changing entrypoint; only view + // getters (the caller's own balances, not an oracle) are reachable during the claim fee transfer. + // slither-disable-next-line reentrancy-eth function bundleJobs( ClaimData[] calldata claims, CancelData[] calldata cancels, @@ -478,7 +484,7 @@ contract Escrow is } function _createLock(uint256 jobId,address token,address payer,uint256 amount,uint256 expiry) internal { require(payer!=address(0),'Invalid payer'); - require(payer!=msg.sender,'Payeer cannot be payee'); + require(payer!=msg.sender,'Payer cannot be payee'); require(token!=address(0),'Invalid token'); require(amount>0,"Invalid amount"); require(jobId>0,"Invalid jobId"); @@ -553,7 +559,7 @@ contract Escrow is } function _reLock(uint256 jobId,address token,address payer,uint256 amount,uint256 expiry) internal { require(payer!=address(0),'Invalid payer'); - require(payer!=msg.sender,'Payeer cannot be payee'); + require(payer!=msg.sender,'Payer cannot be payee'); require(token!=address(0),'Invalid token'); require(amount>0,"Invalid amount"); require(jobId>0,"Invalid jobId"); @@ -649,6 +655,8 @@ contract Escrow is * @param amount amount in wei to claim * @param proof job proof */ + // Reentrancy-safe: nonReentrant blocks re-entry; only view getters are reachable during the transfers. + // slither-disable-next-line reentrancy-eth function claimLockAndWithdraw(uint256 jobId,address token,address payer, uint256 amount,bytes calldata proof) external nonReentrant{ _claimLock(jobId,token,payer,amount,proof); @@ -666,6 +674,8 @@ contract Escrow is * @param amount array amounts in wei to claim * @param proof array of job proofs */ + // Reentrancy-safe: nonReentrant blocks re-entry; only view getters are reachable during the transfers. + // slither-disable-next-line reentrancy-eth function claimLocksAndWithdraw(uint256[] calldata jobId,address[] calldata token, address[] calldata payer,uint256[] calldata amount,bytes[] calldata proof) external nonReentrant{ @@ -684,6 +694,9 @@ contract Escrow is } } + // Reentrancy-safe: reached only via nonReentrant entrypoints, so re-entry is impossible; the + // funds/userTokens writes after the fee transfer cannot be exploited. + // slither-disable-next-line reentrancy-no-eth,reentrancy-benign function _claimLock(uint256 jobId,address token,address payer,uint256 amount, bytes calldata proof) internal { require(payer!=address(0),'Invalid payer'); From 66cd246ac43879721c3239543eba1f0a3d2e6f85 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Fri, 3 Jul 2026 11:17:42 +0300 Subject: [PATCH 3/4] deployed new contracts --- addresses/address.json | 20 ++++++++++---------- scripts/deploy_enterpriseescrow.js | 8 ++++---- scripts/deploy_escrow.js | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/addresses/address.json b/addresses/address.json index 71194e92..f48e8c72 100755 --- a/addresses/address.json +++ b/addresses/address.json @@ -145,10 +145,10 @@ "VestingWalletC": "0x29F74B853C4B8D36273666FB63a3b71c754424Ed", "OceanNodesBooster": "0x73558Ef3bb6543A8107ac032C5a98Da03ceb0eEf", "OPFCommunityFeeCollectorCompute": "0x1705CaF86B86e85804e20FE342f39B17322Ff592", - "Escrow": "0xC98C71A9efeF2a940c73B7986f1D8469f1815F94", + "Escrow": "0x10E71722c34B16440D6889287F3Ed77A74f809BE", "AccessListFactory": "0x7ed6ac4243472adA55408fEE45c5Dff379f7aBE0", "EnterpriseFeeCollector": "0x254302d1Ae1e1200319c885D93D40a8927ACFcD7", - "EnterpriseEscrow": "0x2213FeBd4E681633bE255AfceFbd233EbcFc33c9", + "EnterpriseEscrow": "0x3dE5029154256aC90E483f7a4068388F90f3e940", "FixedPriceEnterprise": "0x6C97D128f7E7D21ac3C722458Dc5d71f7e1bBa6e" }, "goerli": { @@ -305,11 +305,11 @@ "Dispenser": "0x2720d405ef7cDC8a2E2e5AeBC8883C99611d893C", "ERC721Factory": "0xEF62FB495266C72a5212A11Dce8baa79Ec0ABeB1", "OPFCommunityFeeCollectorCompute": "0x0Ee8b03Dafc868a0899ebE1c12d734D53A957250", - "Escrow": "0xd09789Ce05aa0f6feFc18315de4964Db07Cc0618", + "Escrow": "0x98307499910691aaA93A2D753A02ca5d908D3Cf7", "EnterpriseFeeCollector": "0x8c98ea273bA22327F896Aa1a1a46E1BFf56e9b1D", "FixedPriceEnterprise": "0xfa48673a7C36A2A768f89AC1ee8C355D5c367B02", "AccessListFactory": "0x43eC0a34E1b70C7f8E579ab866F37642777727E7", - "EnterpriseEscrow": "0x6fd867E5AEE6D62a24f97939db90C4e67A73A651", + "EnterpriseEscrow": "0x4aA261bC595abC6fDEf0255C83e2a9305261753f", "COMPY": "0x92b368055425f34c18e6f7A80DEaB7Ff106C9d05", "COMPYFaucet": "0xC56663D0c3f535211E5bb238Eb8E80e184A4bA11", "COMPYSwap": "0x7DBDcF3579831ed30666817D53DA7f6D5bC9861F" @@ -339,7 +339,7 @@ "AccessListFactory": "0x12bB8D85a091A69A07E22E52d4567dBB91568f52", "BatchPayments": "0x9497d1d64F2aFeBcd4f9916Eef3d9094E5Df962f", "OPFCommunityFeeCollectorCompute": "0xE5aa2C9B551aFcA4C0A98BB3B37D7A43084d0a66", - "Escrow": "0x0Ee8b03Dafc868a0899ebE1c12d734D53A957250" + "Escrow": "0x86F2BB9F8f18B5a836b342199a3eC89F282E4018" }, "optimism_sepolia": { "chainId": 11155420, @@ -358,11 +358,11 @@ "Dispenser": "0x30E4CC2C7A9c6aA2b2Ce93586E3Df24a3A00bcDD", "ERC721Factory": "0xDEfD0018969cd2d4E648209F876ADe184815f038", "OPFCommunityFeeCollectorCompute": "0xe47991939632757DEe324E8ec9A3268c62C0DB76", - "Escrow": "0x23A8b2D7176485a6349e4830605F323f31019333", + "Escrow": "0x7842Fa3B2d87Ff1cd52C4152382f7C4B3406E5A6", "AccessListFactory": "0xEcD0C3519a081e3924D6F3197f86980eA7dfCf71", "EnterpriseFeeCollector": "0x43eC0a34E1b70C7f8E579ab866F37642777727E7", "FixedPriceEnterprise": "0x6976320eC365202118B4B0B0906E13DDf21633A5", - "EnterpriseEscrow": "0x5494711392a67DA50D3bC7b1fcC2d1877cFaA4d2" + "EnterpriseEscrow": "0xfa48673a7C36A2A768f89AC1ee8C355D5c367B02" }, "optimism": { "chainId": 10, @@ -381,10 +381,10 @@ "Dispenser": "0x2112Eb973af1DBf83a4f11eda82f7a7527D7Fde5", "ERC721Factory": "0x80E63f73cAc60c1662f27D2DFd2EA834acddBaa8", "OPFCommunityFeeCollectorCompute": "0xc37F8341Ac6e4a94538302bCd4d49Cf0852D30C0", - "Escrow": "0x4dD281EB67DED07E76E413Df16176D66ae69e240", + "Escrow": "0xA4E108a4fa07C931cFe4bF2c1c6f9C1579d5a819", "AccessListFactory": "0x4E6058dC00e90C0DCA47A5d0D3346F409939A5ab", "EnterpriseFeeCollector": "0xE9397625Df9B63f0C152f975234b7988b54710B8", - "EnterpriseEscrow": "0xFe7967A5176fDAFa8DE109b3507016B885a82D6e", + "EnterpriseEscrow": "0xe8c6Dc39602031A152440311e364818ba25C2Bc1", "FixedPriceEnterprise": "0x1d535147a97bd87c8443125376E6671B60556E07" }, "pontus-x-devnet": { @@ -421,7 +421,7 @@ "ERC721Factory": "0x159924ca0F47D6F704B97E29099b89e518A17B5E", "BatchPayments": "0xFe7967A5176fDAFa8DE109b3507016B885a82D6e", "OPFCommunityFeeCollectorCompute": "0x7b0576CF01E868bce46cca91b2a8E674141b0355", - "Escrow": "0x43eC0a34E1b70C7f8E579ab866F37642777727E7", + "Escrow": "0xfa48673a7C36A2A768f89AC1ee8C355D5c367B02", "AccessListFactory": "0xE5aa2C9B551aFcA4C0A98BB3B37D7A43084d0a66", "COMPY": "0x5494711392a67DA50D3bC7b1fcC2d1877cFaA4d2", "COMPYFaucet": "0x8c98ea273bA22327F896Aa1a1a46E1BFf56e9b1D", diff --git a/scripts/deploy_enterpriseescrow.js b/scripts/deploy_enterpriseescrow.js index db3620ec..d1ef93d3 100644 --- a/scripts/deploy_enterpriseescrow.js +++ b/scripts/deploy_enterpriseescrow.js @@ -49,7 +49,7 @@ async function main() { case 1: networkName = "mainnet"; gasLimit = 6500000; - gasPrice = ethers.utils.parseUnits("0.08", "gwei"); + gasPrice = ethers.utils.parseUnits("0.12", "gwei"); break; case 10: networkName = "optimism"; @@ -58,13 +58,13 @@ async function main() { break; case 11155111: networkName = "sepolia"; - gasPrice = ethers.utils.parseUnits("5", "gwei"); + gasPrice = ethers.utils.parseUnits("23", "gwei"); gasLimit = 6500000; break; case 11155420: networkName = "optimism_sepolia"; - gasPrice = ethers.utils.parseUnits("0.001000011", "gwei"); - gasLimit = 5000000; + gasPrice = ethers.utils.parseUnits("0.0016", "gwei"); + gasLimit = 6500000; break; } diff --git a/scripts/deploy_escrow.js b/scripts/deploy_escrow.js index a2389902..8d4cb226 100644 --- a/scripts/deploy_escrow.js +++ b/scripts/deploy_escrow.js @@ -50,57 +50,57 @@ async function main() { networkName = "mainnet"; OPFOwner = "0x0d27cd67c4A3fd3Eb9C7C757582f59089F058167"; RouterAddress = "0x8149276f275EEFAc110D74AFE8AFECEaeC7d1593"; - gasLimit = 5000000 - gasPrice = ethers.utils.parseUnits('1.2', 'gwei') + gasLimit = 6500000 + gasPrice = ethers.utils.parseUnits('0.12', 'gwei') break; case 10: networkName = "optimism"; OPFOwner = '0xC7EC1970B09224B317c52d92f37F5e1E4fF6B687' RouterAddress = "0xf26c6C93f9f1d725e149d95f8E7B2334a406aD10"; gasPrice = ethers.utils.parseUnits('0.0010', 'gwei') - gasLimit = 28000000 + gasLimit = 6500000 break; case 0x89: networkName = "polygon"; OPFOwner = "0x6272E00741C16b9A337E29DB672d51Af09eA87dD"; RouterAddress = "0x78e1317186786591912A10a7aF2490B8B4697A93"; - gasLimit = 19000000; + gasLimit = 6500000; gasPrice = ethers.utils.parseUnits('30', 'gwei'); break; case 8453: networkName = "base"; OPFOwner = '0x4169e846f1524Cf0ac02Bd4B04fa33242709Cf64'; RouterAddress = "0xEF62FB495266C72a5212A11Dce8baa79Ec0ABeB1"; - gasPrice = ethers.utils.parseUnits('0.022999855', 'gwei') - gasLimit = 5000000 + gasPrice = ethers.utils.parseUnits('0.05', 'gwei') + gasLimit = 6500000 break; case 23294: networkName = "oasis_sapphire"; OPFOwner = '0x086E7F0588755af5AF5f8194542Fd8328238F3C1' RouterAddress = "0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8"; gasPrice = ethers.utils.parseUnits('100', 'gwei') - gasLimit = 15000000 + gasLimit = 6500000 break; case 23295: networkName = "oasis_saphire_testnet"; OPFOwner = '0xC7EC1970B09224B317c52d92f37F5e1E4fF6B687' RouterAddress = "0x5FBC2A29f7C8e4533dECD14Dc1c561A8eF0d4e31"; gasPrice = ethers.utils.parseUnits('100', 'gwei') - gasLimit = 28000000 + gasLimit = 6500000 break; case 11155111: networkName = "sepolia"; OPFOwner = '0xC7EC1970B09224B317c52d92f37F5e1E4fF6B687'; RouterAddress = "0x2112Eb973af1DBf83a4f11eda82f7a7527D7Fde5"; - gasPrice = ethers.utils.parseUnits('5', 'gwei') - gasLimit = 5000000 + gasPrice = ethers.utils.parseUnits('23', 'gwei') + gasLimit = 6500000 break; case 11155420: networkName = "optimism_sepolia"; OPFOwner = '0xC7EC1970B09224B317c52d92f37F5e1E4fF6B687'; RouterAddress = "0x1B083D8584dd3e6Ff37d04a6e7e82b5F622f3985"; - gasPrice = ethers.utils.parseUnits('1', 'gwei') - gasLimit = 28000000 + gasPrice = ethers.utils.parseUnits('0.0016', 'gwei') + gasLimit = 6500000 break; default: OPFOwner = "0x0d27cd67c4A3fd3Eb9C7C757582f59089F058167"; From 86f18c71b832848786b13010e3b2e8295e2f30c6 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Fri, 3 Jul 2026 11:23:17 +0300 Subject: [PATCH 4/4] v2.9.0 --- .bumpversion.cfg | 2 +- package-lock.json | 2 +- package.json | 2 +- setup.cfg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index bd2a5d95..a4650586 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = v2.8.0 +current_version = v2.9.0 commit = True tag = True diff --git a/package-lock.json b/package-lock.json index 31215e5e..ba3f973d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oceanprotocol/contracts", - "version": "2.8.0", + "version": "2.9.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 46e5ccb8..a97611c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oceanprotocol/contracts", - "version": "2.8.0", + "version": "2.9.0", "description": "Ocean Protocol Smartcontracts", "bugs": { "url": "https://github.com/oceanprotocol/contracts/issues" diff --git a/setup.cfg b/setup.cfg index 4ef3b024..ce29e229 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ocean-contracts -version = v2.8.0 +version = v2.9.0 author = leucothia author_email = devops@oceanprotocol.com description = 🐳 Ocean Protocol L1 - v4