From 9fca6ad6551a39b5a50a6f9bee468c84ef8b0e89 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Fri, 17 Jan 2025 11:49:50 +0200 Subject: [PATCH 1/9] escrow contract --- contracts/escrow/Escrow.sol | 402 ++++++++++++++++++++++++++++++++ package.json | 1 + scripts/check_deployment.js | 2 +- scripts/deploy-contracts.js | 15 ++ test/unit/escrow/Escrow.test.js | 273 ++++++++++++++++++++++ 5 files changed, 692 insertions(+), 1 deletion(-) create mode 100644 contracts/escrow/Escrow.sol create mode 100644 test/unit/escrow/Escrow.test.js diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol new file mode 100644 index 00000000..67182e9b --- /dev/null +++ b/contracts/escrow/Escrow.sol @@ -0,0 +1,402 @@ +pragma solidity 0.8.12; +// Copyright BigchainDB GmbH and Ocean Protocol contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +/** + * @title Escrow contract + * + * @dev escrow contract between payer (aka user, consumer, etc) + * and payee (app/services that is performing a task which needs to be paid). + * + * The payer flow looks like: + * - payer deposits token + * - payer sets limit for payees (max amount, max process time) + * + * + * 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 + */ +contract Escrow is + ReentrancyGuard +{ + using SafeMath for uint256; + using SafeERC20 for IERC20; + + + /* User funds are stored per user and per token */ + struct userFunds{ + uint256 available; + uint256 locked; + } + + mapping(address => mapping(address => userFunds)) private funds; // user -> token -> userFunds + + /* Payee authorizations are stored per user and per token */ + struct auth{ + address payee; + uint256 maxLockedAmount; + uint256 currentLockedAmount; + uint256 maxLockSeconds; + uint256 maxLockCounts; + uint256 currentLocks; + } + + mapping(address => mapping(address => auth[])) private userAuths; // user -> token -> userAuths + + + // locks + struct lock{ + uint256 jobId; + address payer; + address payee; + uint256 amount; + uint256 expiry; + address token; + } + lock[] locks; + + // 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, + uint256 maxLockSeconds,uint256 maxLockCounts); + event Lock(address payer,address payee,uint256 jobId,uint256 amount,uint256 expiry,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); + + /* Payer actions */ + + /** + * @dev deposit + * Called by payer to deposit funds in the contract + * + * @param token token to deposit + * @param amount amount in wei to deposit + */ + function deposit(address token,uint256 amount) external nonReentrant{ + require(token!=address(0),"Invalid token address"); + funds[msg.sender][token].available+=amount; + emit Deposit(msg.sender,token,amount); + _pullUnderlying(token,msg.sender,address(this),amount); + + } + + /** + * @dev withdraw + * Called by payer to withdraw available (not locked) funds from the contract + * + * @param token token to withdraw + * @param amount amount in wei to withdraw + */ + function withdraw(address token,uint256 amount) external nonReentrant{ + require(funds[msg.sender][token].available>=amount,"Not enough available funds"); + funds[msg.sender][token].available-=amount; + emit Withdraw(msg.sender,token,amount); + IERC20(token).safeTransfer( + msg.sender, + amount + ); + } + + /** + * @dev authorize + * Called by payer to authorize a payee to lock and claim funds + * + * @param token token to lock + * @param payee payee address + * @param maxLockedAmount maximum amount locked by payee in one lock + * @param maxLockSeconds maximum lock duration in seconds + * @param maxLockCounts maximum locks held by this payee + */ + function authorize(address token,address payee,uint256 maxLockedAmount, + uint256 maxLockSeconds,uint256 maxLockCounts) external{ + + require(token!=address(0),'Invalid token'); + require(payee!=address(0),'Invalid payee'); + uint256 i; + for(i=0;i0,"Invalid amount"); + require(jobId>0,"Invalid jobId"); + auth memory tempAuth; + uint256 index; + uint256 ts=block.timestamp; + require(funds[payer][token].available>=amount,"Payer does not have enough funds"); + for(index=0;indexts && expiry <= (ts+tempAuth.maxLockSeconds),"Invalid expiry"); + require(amount<= tempAuth.maxLockedAmount,"Amount too high"); + require(tempAuth.currentLockedAmount+amount<=tempAuth.maxLockedAmount,"Exceeds maxLockedAmount"); + require(tempAuth.currentLocks0,'Invalid jobId'); + lock memory tempLock; + uint256 index; + for(index=0;index=amount,"Amount too high"); + + //update auths + for(uint256 i=0;i= balanceBefore.add(amount), + "Transfer amount is too low" + ); + } +} \ No newline at end of file diff --git a/package.json b/package.json index 725ac39b..33727b4b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "scripts": { "test:full": "npx hardhat test test/unit/*/*.test.js test/flow/*.test.js ", "test:batch": "npx hardhat test test/flow/SwapBatch.test.js ", + "test:escrow": "npx hardhat test test/unit/escrow/Escrow.test.js ", "test:fre": "npx hardhat test test/flow/FixedRateExchange.test.js ", "test:dispenser": "npx hardhat test test/flow/Dispenser.test.js ", "test:debug": "npx hardhat test test/flow/Vesting.test.js ", diff --git a/scripts/check_deployment.js b/scripts/check_deployment.js index bd7e01eb..a020a9fe 100644 --- a/scripts/check_deployment.js +++ b/scripts/check_deployment.js @@ -16,7 +16,7 @@ async function testDeployment() { console.error("Missing development network") process.exit(1) } - keys = ["startBlock", "Router", "FixedPrice", "ERC20Template", "ERC721Template", "Dispenser", "ERC721Factory","AccessListFactory"] + keys = ["startBlock", "Router", "FixedPrice", "ERC20Template", "ERC721Template", "Dispenser", "ERC721Factory","AccessListFactory","Escrow"] for (const key of keys) { if (!(key in addresses['development'])) { console.error("Missing " + key + " deployment") diff --git a/scripts/deploy-contracts.js b/scripts/deploy-contracts.js index d9d10d78..883a44e4 100644 --- a/scripts/deploy-contracts.js +++ b/scripts/deploy-contracts.js @@ -901,6 +901,21 @@ async function main() { addresses.BatchPayments = deployBatchPayments.address; if (sleepAmount > 0) await sleep(sleepAmount) + // Escrow + if (logging) console.info("Deploying Escrow"); + const Escrow = await ethers.getContractFactory( + "Escrow", + owner + ); + + const deployEscrow = await Escrow.connect(owner).deploy(options) + await deployEscrow.deployTransaction.wait(); + if (show_verify) { + console.log("\tRun the following to verify on etherscan"); + console.log("\tnpx hardhat verify --network " + networkName + " " + deployEscrow.address) + } + addresses.Escrow = deployEscrow.address; + if (sleepAmount > 0) await sleep(sleepAmount) //DF contracts if (shouldDeployDF) { diff --git a/test/unit/escrow/Escrow.test.js b/test/unit/escrow/Escrow.test.js new file mode 100644 index 00000000..c205ae00 --- /dev/null +++ b/test/unit/escrow/Escrow.test.js @@ -0,0 +1,273 @@ +const { assert,expect } = require('chai'); +const { ethers } = require("hardhat"); +const { json } = require('hardhat/internal/core/params/argumentTypes'); +const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { getEventFromTx } = require("../../helpers/utils") + +const addressZero = '0x0000000000000000000000000000000000000000'; + +const blocktimestamp = async () => { + return (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; +} + +const fastForward = async (seconds) => { + await ethers.provider.send("evm_increaseTime", [seconds]); + await ethers.provider.send("evm_mine"); +} +// Start test block +describe('Escrow tests', function () { + let Mock20Contract; + let Mock20DecimalsContract; + let EscrowContract; + let signers; + let payee1,payee2,payee3,payer1,payer2,payer3; + before(async function () { + // Get the contractOwner and collector address + signers = await ethers.getSigners(); + payee1=signers[1] + payee2=signers[2] + payee3=signers[3] + payer1=signers[4] + payer2=signers[5] + payer3=signers[6] + const MockErc20 = await ethers.getContractFactory('MockERC20'); + const MockErc20Decimals = await ethers.getContractFactory('MockERC20Decimals'); + const Escrow = await ethers.getContractFactory('Escrow'); + Mock20Contract = await MockErc20.deploy(signers[0].address,"MockERC20", 'MockERC20'); + Mock20DecimalsContract = await MockErc20Decimals.deploy("Mock6Digits", 'Mock6Digits', 6); + EscrowContract = await Escrow.deploy(); + await Mock20Contract.deployed(); + await Mock20DecimalsContract.deployed(); + await EscrowContract.deployed(); + // top up accounts + await Mock20Contract.transfer(payer1.address,web3.utils.toWei("10000")) + await Mock20Contract.transfer(payer2.address,web3.utils.toWei("10000")) + await Mock20Contract.transfer(payer3.address,web3.utils.toWei("10000")) + await Mock20DecimalsContract.transfer(payer1.address,ethers.utils.parseUnits("10000", 6)) + await Mock20DecimalsContract.transfer(payer2.address,ethers.utils.parseUnits("10000", 6)) + await Mock20DecimalsContract.transfer(payer3.address,ethers.utils.parseUnits("10000", 6)) + + }); + + + // Test cases + it('Check contract deployment', async function () { + expect(await EscrowContract.address).to.exist; + +}); + +it('Escrow - deposit', async function () { + expect(await Mock20Contract.balanceOf(EscrowContract.address)).to.equal(0); + expect(await Mock20DecimalsContract.balanceOf(EscrowContract.address)).to.equal(0); + await Mock20Contract.connect(payer1).approve(EscrowContract.address, web3.utils.toWei("10000")); + await EscrowContract.connect(payer1).deposit(Mock20Contract.address,web3.utils.toWei("100")); + + expect(await Mock20Contract.balanceOf(EscrowContract.address)).to.equal(web3.utils.toWei("100")); + expect(await Mock20DecimalsContract.balanceOf(EscrowContract.address)).to.equal(0); + const funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + expect(funds.available).to.equal(web3.utils.toWei("100")) + expect(funds.locked).to.equal(0) + const locks=await EscrowContract.connect(payer1).getLocks(addressZero,addressZero,addressZero) + expect(locks.length).to.equal(0) + const auths=await EscrowContract.connect(payer1).getAuthorizations(Mock20Contract.address,payer1.address,addressZero) + expect(auths.length).to.equal(0) + +}); + +it('Escrow - withdraw', async function () { + const balanceMock20=await Mock20Contract.balanceOf(EscrowContract.address); + const balanceMock20Decimal=await Mock20DecimalsContract.balanceOf(EscrowContract.address); + await expect(EscrowContract.connect(payer1).withdraw(Mock20Contract.address,web3.utils.toWei("10000"))).to.be.revertedWith("Not enough available funds") + await EscrowContract.connect(payer1).withdraw(Mock20Contract.address,web3.utils.toWei("10")); + expect(await Mock20Contract.balanceOf(EscrowContract.address)).to.equal(web3.utils.toWei("90")); +}); + +it('Escrow - auth', async function () { + await EscrowContract.connect(payer1).authorize(Mock20Contract.address,payee1.address,web3.utils.toWei("50"),100,2); + const auths=await EscrowContract.connect(payer1).getAuthorizations(Mock20Contract.address,payer1.address,payee1.address) + expect(auths.length).to.equal(1) + expect(auths[0].payee).to.equal(payee1.address) + expect(auths[0].maxLockedAmount).to.equal(web3.utils.toWei("50")) + expect(auths[0].maxLockSeconds).to.equal(100) + expect(auths[0].maxLockSeconds).to.equal(100) + expect(auths[0].maxLockCounts).to.equal(2) + expect(auths[0].currentLocks).to.equal(0) + +}); + +it('Escrow - lock', async function () { + + let jobId=1 // full claim + const now=Math.floor(Date.now() / 1000) + const expire = Math.round(await blocktimestamp()) + 60 + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer2.address,web3.utils.toWei("50"),expire)).to.be.revertedWith("Payer does not have enough funds") + //payer2 has funds, but no auth + await Mock20Contract.connect(payer2).approve(EscrowContract.address, web3.utils.toWei("10000")); + await EscrowContract.connect(payer2).deposit(Mock20Contract.address,web3.utils.toWei("100")); + + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer2.address,web3.utils.toWei("50"),expire)).to.be.revertedWith("No auth found") + + //payee1 tries to lock too much + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("60"),expire)).to.be.revertedWith("Amount too high") + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,0,expire)).to.be.revertedWith("Invalid amount") + await expect(EscrowContract.connect(payee1).createLock(0,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire)).to.be.revertedWith("Invalid jobId") + await EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire) + let locks=await EscrowContract.connect(payer1).getLocks(addressZero,addressZero,addressZero) + expect(locks.length).to.equal(1) + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire)).to.be.revertedWith("JobId already exists") + jobId=2 // partial claim + await EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire) + locks=await EscrowContract.connect(payer1).getLocks(addressZero,addressZero,addressZero) + expect(locks.length).to.equal(2) + // previous auth had only 2 concurent locks + await expect(EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire)).to.be.revertedWith("Exceeds maxLockCounts") + await EscrowContract.connect(payer1).authorize(Mock20Contract.address,payee1.address,web3.utils.toWei("50"),100,10); + jobId=3 // expired + await EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire) + locks=await EscrowContract.connect(payer1).getLocks(addressZero,addressZero,addressZero) + expect(locks.length).to.equal(3) + jobId=4 // unclaimed + await EscrowContract.connect(payee1).createLock(jobId,Mock20Contract.address,payer1.address,web3.utils.toWei("10"),expire) + locks=await EscrowContract.connect(payer1).getLocks(addressZero,addressZero,addressZero) + expect(locks.length).to.equal(4) + }); + + it('Escrow - claim entire amount', async function () { + const payer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const payer1Available=payer1Funds.available + const payer1Locked=payer1Funds.locked + const payee1Balance=await Mock20Contract.balanceOf(payee1.address) + // claim jobId + let jobId=1 // full claim + let lock + const allLocks=await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address) + for( oneLock in allLocks){ + if(allLocks[oneLock].jobId==jobId){ + lock=allLocks[oneLock] + } + } + expect(lock.jobId).to.equal(jobId) + const tx=await EscrowContract.connect(payee1).claimLock(lock.jobId,lock.token,lock.payer,lock.amount,0); + const txReceipt = await tx.wait(); + const event = getEventFromTx(txReceipt, 'Claimed') + assert(event, "Cannot find Claimed event") + const afterpayer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const afterpayer1Available=afterpayer1Funds.available + const afterpayer1Locked=afterpayer1Funds.locked + const afterpayee1Balance=await Mock20Contract.balanceOf(payee1.address) + expect(afterpayer1Available).to.equal(payer1Available) + expect(afterpayer1Locked).to.equal(payer1Locked.sub(lock.amount)) + expect(afterpayee1Balance).to.equal(payee1Balance.add(lock.amount)) + // make sure lock is gone + for( oneLock of await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address)){ + expect(oneLock.jobId).to.not.equal(jobId) + } + + + }); + it('Escrow - claim half amount', async function () { + const payer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const payer1Available=payer1Funds.available + const payer1Locked=payer1Funds.locked + const payee1Balance=await Mock20Contract.balanceOf(payee1.address) + // claim jobId + let jobId=2 // partial claim + let lock + const allLocks=await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address) + for( oneLock in allLocks){ + if(allLocks[oneLock].jobId==jobId){ + lock=allLocks[oneLock] + } + } + expect(lock.jobId).to.equal(jobId) + const claimedAmount=web3.utils.toWei("1") + const returnAmount=lock.amount.sub(claimedAmount) + const tx=await EscrowContract.connect(payee1).claimLock(lock.jobId,lock.token,lock.payer,claimedAmount,0); + const txReceipt = await tx.wait(); + const event = getEventFromTx(txReceipt, 'Claimed') + assert(event, "Cannot find Claimed event") + + const afterpayer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const afterpayer1Available=afterpayer1Funds.available + const afterpayer1Locked=afterpayer1Funds.locked + const afterpayee1Balance=await Mock20Contract.balanceOf(payee1.address) + expect(afterpayer1Available).to.equal(payer1Available.add(returnAmount)) + expect(afterpayer1Locked).to.equal(payer1Locked.sub(lock.amount)) + expect(afterpayee1Balance).to.equal(payee1Balance.add(claimedAmount)) + // make sure lock is gone + for( oneLock of await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address)){ + expect(oneLock.jobId).to.not.equal(jobId) + } + }); + + it('Escrow - claim expired lock', async function () { + await fastForward(60) + const payer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const payer1Available=payer1Funds.available + const payer1Locked=payer1Funds.locked + const payee1Balance=await Mock20Contract.balanceOf(payee1.address) + // claim jobId + let jobId=3 // expired lock + let lock + const allLocks=await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address) + for( oneLock in allLocks){ + if(allLocks[oneLock].jobId==jobId){ + lock=allLocks[oneLock] + } + } + expect(lock.jobId).to.equal(jobId) + const claimedAmount=web3.utils.toWei("1") + const returnAmount=lock.amount.sub(claimedAmount) + const tx=await EscrowContract.connect(payee1).claimLock(lock.jobId,lock.token,lock.payer,claimedAmount,0); + const txReceipt = await tx.wait(); + const event = getEventFromTx(txReceipt, 'Canceled') + assert(event, "Cannot find Canceled event") + + const afterpayer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const afterpayer1Available=afterpayer1Funds.available + const afterpayer1Locked=afterpayer1Funds.locked + const afterpayee1Balance=await Mock20Contract.balanceOf(payee1.address) + expect(afterpayer1Available).to.equal(payer1Available.add(lock.amount)) + expect(afterpayer1Locked).to.equal(payer1Locked.sub(lock.amount)) + expect(afterpayee1Balance).to.equal(payee1Balance) + // make sure lock is gone + for( oneLock of await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address)){ + expect(oneLock.jobId).to.not.equal(jobId) + } + }); + it('Escrow - payee cancels expired lock', async function () { + await fastForward(60) + const payer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const payer1Available=payer1Funds.available + const payer1Locked=payer1Funds.locked + const payee1Balance=await Mock20Contract.balanceOf(payee1.address) + // claim jobId + let jobId=4 // unclaimed expired + let lock + const allLocks=await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address) + for( oneLock in allLocks){ + if(allLocks[oneLock].jobId==jobId){ + lock=allLocks[oneLock] + } + } + expect(lock.jobId).to.equal(jobId) + const claimedAmount=web3.utils.toWei("1") + const returnAmount=lock.amount.sub(claimedAmount) + const tx=await EscrowContract.connect(payer1).cancelExpiredLocks(lock.jobId,lock.token,lock.payer,lock.payee); + const txReceipt = await tx.wait(); + const event = getEventFromTx(txReceipt, 'Canceled') + assert(event, "Cannot find Canceled event") + + const afterpayer1Funds=await EscrowContract.connect(payer1).getFunds(Mock20Contract.address) + const afterpayer1Available=afterpayer1Funds.available + const afterpayer1Locked=afterpayer1Funds.locked + const afterpayee1Balance=await Mock20Contract.balanceOf(payee1.address) + expect(afterpayer1Available).to.equal(payer1Available.add(lock.amount)) + expect(afterpayer1Locked).to.equal(payer1Locked.sub(lock.amount)) + expect(afterpayee1Balance).to.equal(payee1Balance) + // make sure lock is gone + for( oneLock of await EscrowContract.connect(payee1).getLocks(Mock20Contract.address,payer1.address,payee1.address)){ + expect(oneLock.jobId).to.not.equal(jobId) + } + }); +}); \ No newline at end of file From b21eca4de10c434de5050191dd4c46ce38d1c83e Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Fri, 17 Jan 2025 12:04:13 +0200 Subject: [PATCH 2/9] use cached lengths for loops --- contracts/escrow/Escrow.sol | 40 +++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index 67182e9b..285ac2dc 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -124,7 +124,8 @@ contract Escrow is require(token!=address(0),'Invalid token'); require(payee!=address(0),'Invalid payee'); uint256 i; - for(i=0;i0,"Invalid amount"); require(jobId>0,"Invalid jobId"); - auth memory tempAuth; + auth memory tempAuth=auth(address(0),0,0,0,0,0); uint256 index; uint256 ts=block.timestamp; require(funds[payer][token].available>=amount,"Payer does not have enough funds"); - for(index=0;index0,'Invalid jobId'); lock memory tempLock; uint256 index; - for(index=0;index=amount,"Amount too high"); //update auths - for(uint256 i=0;i Date: Fri, 17 Jan 2025 12:12:12 +0200 Subject: [PATCH 3/9] initialize lock --- contracts/escrow/Escrow.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index 285ac2dc..009030d3 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -285,7 +285,7 @@ contract Escrow is require(payer!=address(0),'Invalid payer'); require(token!=address(0),'Invalid token'); require(jobId>0,'Invalid jobId'); - lock memory tempLock; + lock memory tempLock=lock(0,address(0),address(0),0,0,address(0)); uint256 index; uint256 length=locks.length; for(index=0;index Date: Fri, 31 Jan 2025 16:25:00 +0200 Subject: [PATCH 4/9] add getFunds for specific user --- contracts/escrow/Escrow.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index 009030d3..c51f9741 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -150,6 +150,9 @@ contract Escrow is function getFunds(address token) public view returns (userFunds memory){ return(funds[msg.sender][token]); } + function getFunds(address payer,address token) public view returns (userFunds memory){ + return(funds[payer][token]); + } /** * @dev getLocks From c3975d2f47047c9db79d663a611717a1b761100e Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Fri, 31 Jan 2025 16:32:33 +0200 Subject: [PATCH 5/9] small changes --- contracts/escrow/Escrow.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index c51f9741..dacd62c6 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -150,7 +150,14 @@ contract Escrow is function getFunds(address token) public view returns (userFunds memory){ return(funds[msg.sender][token]); } - function getFunds(address payer,address token) public view returns (userFunds memory){ + /** + * @dev getUserFunds + * Returns funds information for a specific payer + * + * @param payer payer + * @param token token + */ + function getUserFunds(address payer,address token) public view returns (userFunds memory){ return(funds[payer][token]); } From 9831aba287f2e84e192b62a531be7986501081bb Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Sun, 2 Feb 2025 14:02:28 +0200 Subject: [PATCH 6/9] add mainnet to hardhat config --- hardhat.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat.config.js b/hardhat.config.js index a1fc0666..0de625b5 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -192,6 +192,7 @@ module.exports = { optimism_sepolia: process.env.ETHERSCAN_API_KEY, optimism: process.env.ETHERSCAN_API_KEY, sepolia: process.env.ETHERSCAN_API_KEY, + mainnet: process.env.ETHERSCAN_API_KEY, }, customChains: [ { From dd5912d2cab1eb5504c10c9060c4dec45f308fe8 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Sun, 2 Feb 2025 14:03:00 +0200 Subject: [PATCH 7/9] make expiry relative in createLock --- contracts/escrow/Escrow.sol | 5 ++--- test/unit/escrow/Escrow.test.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/escrow/Escrow.sol b/contracts/escrow/Escrow.sol index dacd62c6..f5c59c51 100644 --- a/contracts/escrow/Escrow.sol +++ b/contracts/escrow/Escrow.sol @@ -246,7 +246,6 @@ contract Escrow is require(jobId>0,"Invalid jobId"); auth memory tempAuth=auth(address(0),0,0,0,0,0); uint256 index; - uint256 ts=block.timestamp; require(funds[payer][token].available>=amount,"Payer does not have enough funds"); uint256 length=userAuths[payer][token].length; for(index=0;indexts && expiry <= (ts+tempAuth.maxLockSeconds),"Invalid expiry"); + require(expiry<=tempAuth.maxLockSeconds,"Expiry too high"); require(amount<= tempAuth.maxLockedAmount,"Amount too high"); require(tempAuth.currentLockedAmount+amount<=tempAuth.maxLockedAmount,"Exceeds maxLockedAmount"); require(tempAuth.currentLocks Date: Mon, 3 Feb 2025 13:30:12 +0200 Subject: [PATCH 8/9] deploy escrow --- addresses/address.json | 12 +++-- scripts/deploy_escrow.js | 105 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 scripts/deploy_escrow.js diff --git a/addresses/address.json b/addresses/address.json index 0c2d5e01..d6234a65 100755 --- a/addresses/address.json +++ b/addresses/address.json @@ -142,7 +142,8 @@ "VestingWallet0": "0x8D011915C437AD5f5e3B0E4c6dd6380c92599f99", "VestingWalletA": "0xf9FB1f54eA825734E3a77e73A3864f4B46C815d9", "VestingWalletB": "0xf02e3163Dc3409D69D88D7AcDA613432E9A18741", - "VestingWalletC": "0x29F74B853C4B8D36273666FB63a3b71c754424Ed" + "VestingWalletC": "0x29F74B853C4B8D36273666FB63a3b71c754424Ed", + "OceanNodesBooster": "0x73558Ef3bb6543A8107ac032C5a98Da03ceb0eEf" }, "goerli": { "chainId": 5, @@ -296,7 +297,8 @@ "1": "0x9C9eE07b8Ce907D2f9244F8317C1Ed29A3193bAe" }, "Dispenser": "0x2720d405ef7cDC8a2E2e5AeBC8883C99611d893C", - "ERC721Factory": "0xEF62FB495266C72a5212A11Dce8baa79Ec0ABeB1" + "ERC721Factory": "0xEF62FB495266C72a5212A11Dce8baa79Ec0ABeB1", + "Escrow": "0x7b0576CF01E868bce46cca91b2a8E674141b0355" }, "oasis_saphire": { "chainId": 23294, @@ -321,7 +323,8 @@ "DFStrategyV1": "0x3c21a90599b5B7f37014cA5Bf30d3f1b73d7e391", "PredictoorHelper": "0xE9397625Df9B63f0C152f975234b7988b54710B8", "AccessListFactory": "0x12bB8D85a091A69A07E22E52d4567dBB91568f52", - "BatchPayments": "0x9497d1d64F2aFeBcd4f9916Eef3d9094E5Df962f" + "BatchPayments": "0x9497d1d64F2aFeBcd4f9916Eef3d9094E5Df962f", + "Escrow": "0x7b0576CF01E868bce46cca91b2a8E674141b0355" }, "optimism_sepolia": { "chainId": 11155420, @@ -338,7 +341,8 @@ "1": "0x3C5605202eD47C162450AE975415473e73F93072" }, "Dispenser": "0x30E4CC2C7A9c6aA2b2Ce93586E3Df24a3A00bcDD", - "ERC721Factory": "0xDEfD0018969cd2d4E648209F876ADe184815f038" + "ERC721Factory": "0xDEfD0018969cd2d4E648209F876ADe184815f038", + "Escrow": "0xA0329eFFa1370eAb1DC5998Db4292ae0F535a282" }, "optimism": { "chainId": 10, diff --git a/scripts/deploy_escrow.js b/scripts/deploy_escrow.js new file mode 100644 index 00000000..aa787349 --- /dev/null +++ b/scripts/deploy_escrow.js @@ -0,0 +1,105 @@ +// We require the Hardhat Runtime Environment explicitly here. This is optional +// but useful for running the script in a standalone fashion through `node