Add lock and Release tests. Add instructions to run contracts locally.
This commit is contained in:
parent
0f35eec623
commit
1892e0bd9b
23
README.md
23
README.md
@ -22,6 +22,29 @@ Then use a Contract instance to interact directly with it:
|
||||
const p2pixContract = new ethers.Contract(address, P2PIXArtifact.abi, signer);
|
||||
```
|
||||
|
||||
## Deploying local environment
|
||||
|
||||
Clone the repo and install dependencies:
|
||||
```
|
||||
git clone https://github.com/doiim/p2pix-smart-contracts.git
|
||||
cd p2pix-smart-contract
|
||||
npm install
|
||||
```
|
||||
|
||||
On the first teminal use the following command and import some wallets to your Metamask and connect to the network pointed:
|
||||
```
|
||||
npx hardhat node
|
||||
```
|
||||
|
||||
On the second teminal run following commands:
|
||||
```
|
||||
npx hardhat run --network localhost scripts/1-deploy-p2pix.js
|
||||
npx hardhat run --network localhost scripts/2-deploy-mockToken.js
|
||||
```
|
||||
|
||||
The second script transfer 2M tokens to the firrs wallet of the node.
|
||||
To use the P2Pix smart contract first transfer some of the tokens to other wallets.
|
||||
|
||||
## Testing
|
||||
|
||||
To run tests, clone this repo, install dependencies and run Hardhat tests.
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/5ce31c4ec8418c4fef71a52ed732efd0.json"
|
||||
"buildInfo": "../../build-info/7e909668316aeda4fb9591ef4393e389.json"
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ contract P2PIX {
|
||||
event DepositClosed(address indexed seller, bytes32 depositID);
|
||||
event DepositWithdrawn(address indexed seller, bytes32 depositID, uint256 amount);
|
||||
event DepositPriceChanged(bytes32 indexed depositID, uint256 price);
|
||||
event LockAdded(address indexed buyer, bytes32 indexed lockID, bytes32 depositID, uint256 amount);
|
||||
event LockAdded(address indexed buyer, bytes32 indexed lockID, bytes32 depositID, uint256 amount, uint256 locked);
|
||||
event LockReleased(address indexed buyer, bytes32 lockId);
|
||||
event LockReturned(address indexed buyer, bytes32 lockId);
|
||||
|
||||
@ -95,7 +95,8 @@ contract P2PIX {
|
||||
unlockExpired(expiredLocks);
|
||||
Deposit storage d = mapDeposits[depositID];
|
||||
require(d.valid, "P2PIX: Deposit not valid anymore");
|
||||
require(d.remaining > amount/d.price, "P2PIX: Not enough remaining");
|
||||
uint256 toLock = (amount * 1 ether) / d.price;
|
||||
require(d.remaining >= toLock, "P2PIX: Not enough token remaining on deposit");
|
||||
lockID = keccak256(abi.encodePacked(depositID, amount, targetAddress));
|
||||
require(
|
||||
mapLocks[lockID].expirationBlock < block.number,
|
||||
@ -107,12 +108,12 @@ contract P2PIX {
|
||||
relayerAddress,
|
||||
relayerPremium,
|
||||
amount,
|
||||
amount/d.price,
|
||||
toLock,
|
||||
block.number+defaultLockBlocks
|
||||
);
|
||||
mapLocks[lockID] = l;
|
||||
d.remaining -= amount;
|
||||
emit LockAdded(targetAddress, lockID, depositID, amount);
|
||||
d.remaining -= toLock;
|
||||
emit LockAdded(targetAddress, lockID, depositID, amount, toLock);
|
||||
}
|
||||
|
||||
// Relayer interage com o smart contract, colocando no calldata o comprovante do PIX realizado.
|
||||
@ -124,41 +125,43 @@ contract P2PIX {
|
||||
bytes32 s,
|
||||
uint8 v
|
||||
) public {
|
||||
// TODO Check if lockID exists and is enabled
|
||||
// TODO **Prevenir que um Pix não relacionado ao APP seja usado pois tem o mesmo destino
|
||||
Lock storage l = mapLocks[lockID];
|
||||
require(l.expirationBlock > block.number && l.locked > 0, "P2PIX: Lock already released or returned");
|
||||
Deposit storage d = mapDeposits[l.depositID];
|
||||
bytes32 message = keccak256(abi.encodePacked(
|
||||
mapDeposits[l.depositID].pixTarget,
|
||||
l.amount,
|
||||
pixTimestamp
|
||||
));
|
||||
require(!usedTransactions[message], "Transaction already used to unlock payment.");
|
||||
address signer = ecrecover(message, v, r, s);
|
||||
require(validBacenSigners[signer], "Signer is not a valid signer.");
|
||||
bytes32 messageDigest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
|
||||
require(!usedTransactions[message], "P2PIX: Transaction already used to unlock payment");
|
||||
address signer = ecrecover(messageDigest, v, r, s);
|
||||
require(validBacenSigners[signer], "P2PIX: Signer is not a valid signer");
|
||||
IERC20 t = IERC20(d.token);
|
||||
t.transfer(l.targetAddress, l.locked-l.relayerPremium);
|
||||
if (l.relayerPremium > 0) t.transfer(l.relayerAddress, l.relayerPremium);
|
||||
l.amount = 0;
|
||||
l.locked = 0;
|
||||
l.expirationBlock = 0;
|
||||
usedTransactions[message] = true;
|
||||
emit LockReleased(l.targetAddress, lockID);
|
||||
}
|
||||
|
||||
// Change price for deposit amount
|
||||
function changeDepositPrice(bytes32 depositID, uint256 price) public {
|
||||
function changeDepositPrice(bytes32 depositID, uint256 price) public onlySeller(depositID) {
|
||||
Deposit storage d = mapDeposits[depositID];
|
||||
d.price = price;
|
||||
emit DepositPriceChanged(depositID, price);
|
||||
}
|
||||
|
||||
// Unlock expired locks
|
||||
function unlockExpired(bytes32[] calldata lockIDs) internal {
|
||||
function unlockExpired(bytes32[] calldata lockIDs) public {
|
||||
uint256 locksSize = lockIDs.length;
|
||||
for (uint16 i = 0; i < locksSize; i++){
|
||||
Lock storage l = mapLocks[lockIDs[i]];
|
||||
require(l.expirationBlock < block.number && l.amount > 0, "P2PIX: Lock not expired or already paid");
|
||||
mapDeposits[l.depositID].remaining += l.amount;
|
||||
l.amount = 0;
|
||||
require(l.expirationBlock < block.number && l.locked > 0, "P2PIX: Lock not expired or already released");
|
||||
mapDeposits[l.depositID].remaining += l.locked;
|
||||
l.locked = 0;
|
||||
emit LockReturned(l.targetAddress, lockIDs[i]);
|
||||
}
|
||||
}
|
||||
@ -172,8 +175,8 @@ contract P2PIX {
|
||||
Deposit storage d = mapDeposits[depositID];
|
||||
if (d.valid) cancelDeposit(depositID);
|
||||
IERC20 token = IERC20(d.token);
|
||||
token.transfer(d.seller, d.remaining);
|
||||
// Withdraw remaining tokens from mapDeposit[depositID]
|
||||
token.transfer(d.seller, d.remaining);
|
||||
uint256 amount = d.remaining;
|
||||
d.remaining = 0;
|
||||
emit DepositWithdrawn(msg.sender, depositID, amount);
|
||||
|
@ -6,7 +6,7 @@ describe("P2PIX lock/release test", function () {
|
||||
let owner, wallet2, wallet3, wallet4;
|
||||
let p2pix; // Contract instance
|
||||
let erc20; // Token instance
|
||||
let depositID;
|
||||
let depositID, lockID;
|
||||
|
||||
it("Will deploy contracts", async function () {
|
||||
|
||||
@ -20,12 +20,13 @@ describe("P2PIX lock/release test", function () {
|
||||
expect(await erc20.balanceOf(owner.address)).to.equal(ethers.utils.parseEther('20000000', 'wei'));
|
||||
|
||||
const P2PIX = await ethers.getContractFactory("P2PIX");
|
||||
p2pix = await P2PIX.deploy(2, [owner.address, wallet2.address]);
|
||||
p2pix = await P2PIX.deploy(3, [owner.address, wallet2.address]);
|
||||
await p2pix.deployed();
|
||||
|
||||
// Verify values at deployment
|
||||
expect(await p2pix.validBacenSigners(owner.address)).to.equal(true);
|
||||
expect(await p2pix.validBacenSigners(wallet2.address)).to.equal(true);
|
||||
expect(await p2pix.validBacenSigners(wallet3.address)).to.equal(false);
|
||||
});
|
||||
|
||||
it("Should allow create a deposit", async function () {
|
||||
@ -51,6 +52,7 @@ describe("P2PIX lock/release test", function () {
|
||||
ethers.utils.parseEther('0.99'),
|
||||
ethers.utils.parseEther('1000')
|
||||
)
|
||||
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
|
||||
})
|
||||
|
||||
it("Should allow create a new lock", async function () {
|
||||
@ -62,7 +64,7 @@ describe("P2PIX lock/release test", function () {
|
||||
ethers.utils.parseEther('100'),
|
||||
[]
|
||||
)
|
||||
const lockID = ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'address'], [
|
||||
lockID = ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'address'], [
|
||||
depositID,
|
||||
ethers.utils.parseEther('100'),
|
||||
wallet3.address
|
||||
@ -71,8 +73,172 @@ describe("P2PIX lock/release test", function () {
|
||||
wallet3.address,
|
||||
lockID,
|
||||
depositID,
|
||||
ethers.utils.parseEther('100')
|
||||
ethers.utils.parseEther('100'),
|
||||
"101010101010101010101"
|
||||
)
|
||||
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
|
||||
})
|
||||
|
||||
it("Should release the locked amount to the buyer", async function () {
|
||||
const endtoendID = '123';
|
||||
const messageToSign = ethers.utils.solidityKeccak256(['string', 'uint256', 'uint256'], [
|
||||
'SELLER PIX KEY',
|
||||
ethers.utils.parseEther('100'),
|
||||
endtoendID
|
||||
])
|
||||
// Note: messageToSign is a string, that is 66-bytes long, to sign the
|
||||
// binary value, we must convert it to the 32 byte Array that
|
||||
// the string represents
|
||||
//
|
||||
// i.e.
|
||||
// // 66-byte string
|
||||
// "0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba"
|
||||
// ... vs ...
|
||||
// // 32 entry Uint8Array
|
||||
// [ 89, 47, 167, 67, 136, 159, ... 103, 7, 186]
|
||||
const messageHashBytes = ethers.utils.arrayify(messageToSign)
|
||||
// Sign the string message
|
||||
const flatSig = await owner.signMessage(messageHashBytes);
|
||||
// For Solidity, we need the expanded-format of a signature
|
||||
const sig = ethers.utils.splitSignature(flatSig);
|
||||
transaction = await p2pix.connect(wallet3).release(
|
||||
lockID,
|
||||
endtoendID,
|
||||
sig.r,
|
||||
sig.s,
|
||||
sig.v
|
||||
)
|
||||
await expect(transaction).to.emit(p2pix, 'LockReleased').withArgs(
|
||||
wallet3.address,
|
||||
lockID
|
||||
)
|
||||
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
|
||||
expect(await erc20.balanceOf(wallet3.address)).to.equal("101010101010101010101");
|
||||
})
|
||||
|
||||
it("Should allow recreate same lock", async function () {
|
||||
transaction = await p2pix.connect(wallet3).lock(
|
||||
depositID,
|
||||
wallet3.address,
|
||||
ethers.constants.AddressZero,
|
||||
'0',
|
||||
ethers.utils.parseEther('100'),
|
||||
[]
|
||||
)
|
||||
lockID = ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'address'], [
|
||||
depositID,
|
||||
ethers.utils.parseEther('100'),
|
||||
wallet3.address
|
||||
])
|
||||
await expect(transaction).to.emit(p2pix, 'LockAdded').withArgs(
|
||||
wallet3.address,
|
||||
lockID,
|
||||
depositID,
|
||||
ethers.utils.parseEther('100'),
|
||||
"101010101010101010101"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should prevent create again same lock", async function () {
|
||||
await expect(p2pix.connect(wallet3).lock(
|
||||
depositID,
|
||||
wallet3.address,
|
||||
ethers.constants.AddressZero,
|
||||
'0',
|
||||
ethers.utils.parseEther('100'),
|
||||
[]
|
||||
)).to.be.revertedWith('P2PIX: Another lock with same ID is not expired yet');
|
||||
})
|
||||
|
||||
it("Should release the locked amount to the buyer", async function () {
|
||||
const endtoendID = '124';
|
||||
const messageToSign = ethers.utils.solidityKeccak256(['string', 'uint256', 'uint256'], [
|
||||
'SELLER PIX KEY',
|
||||
ethers.utils.parseEther('100'),
|
||||
endtoendID
|
||||
])
|
||||
const messageHashBytes = ethers.utils.arrayify(messageToSign)
|
||||
const flatSig = await owner.signMessage(messageHashBytes);
|
||||
const sig = ethers.utils.splitSignature(flatSig);
|
||||
transaction = await p2pix.connect(wallet3).release(
|
||||
lockID,
|
||||
endtoendID,
|
||||
sig.r,
|
||||
sig.s,
|
||||
sig.v
|
||||
)
|
||||
expect(await erc20.balanceOf(wallet3.address)).to.equal("202020202020202020202");
|
||||
})
|
||||
|
||||
it("Should prevent release again the lock", async function () {
|
||||
const endtoendID = '125';
|
||||
const messageToSign = ethers.utils.solidityKeccak256(['string', 'uint256', 'uint256'], [
|
||||
'SELLER PIX KEY',
|
||||
ethers.utils.parseEther('100'),
|
||||
endtoendID
|
||||
])
|
||||
const messageHashBytes = ethers.utils.arrayify(messageToSign)
|
||||
const flatSig = await owner.signMessage(messageHashBytes);
|
||||
const sig = ethers.utils.splitSignature(flatSig);
|
||||
await expect(p2pix.connect(wallet3).release(
|
||||
lockID,
|
||||
endtoendID,
|
||||
sig.r,
|
||||
sig.s,
|
||||
sig.v
|
||||
)).to.be.revertedWith('P2PIX: Lock already released or returned');
|
||||
})
|
||||
|
||||
it("Should prevent create a 800 lock", async function () {
|
||||
await expect(p2pix.connect(wallet3).lock(
|
||||
depositID,
|
||||
wallet3.address,
|
||||
ethers.constants.AddressZero,
|
||||
'0',
|
||||
ethers.utils.parseEther('800'),
|
||||
[]
|
||||
)).to.be.revertedWith('P2PIX: Not enough token remaining on deposit');
|
||||
})
|
||||
|
||||
it("Should allow recreate same lock again", async function () {
|
||||
transaction = await p2pix.connect(wallet3).lock(
|
||||
depositID,
|
||||
wallet3.address,
|
||||
ethers.constants.AddressZero,
|
||||
'0',
|
||||
ethers.utils.parseEther('100'),
|
||||
[]
|
||||
)
|
||||
lockID = ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'address'], [
|
||||
depositID,
|
||||
ethers.utils.parseEther('100'),
|
||||
wallet3.address
|
||||
])
|
||||
await expect(transaction).to.emit(p2pix, 'LockAdded').withArgs(
|
||||
wallet3.address,
|
||||
lockID,
|
||||
depositID,
|
||||
ethers.utils.parseEther('100'),
|
||||
"101010101010101010101"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should allow unlock expired lock", async function () {
|
||||
await expect(p2pix.unlockExpired([lockID]))
|
||||
.to.be.revertedWith('P2PIX: Lock not expired or already released');
|
||||
await network.provider.send("evm_mine");
|
||||
await network.provider.send("evm_mine");
|
||||
await network.provider.send("evm_mine");
|
||||
transaction = await p2pix.unlockExpired([lockID])
|
||||
await expect(transaction).to.emit(p2pix, 'LockReturned').withArgs(
|
||||
wallet3.address,
|
||||
lockID
|
||||
)
|
||||
})
|
||||
|
||||
it("Should prevent unlock again", async function () {
|
||||
await expect(p2pix.unlockExpired([lockID]))
|
||||
.to.be.revertedWith('P2PIX: Lock not expired or already released');
|
||||
})
|
||||
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user