Replace deposit prices for ETH premium.

This commit is contained in:
Filipe Soccol 2022-11-09 16:29:27 -03:00
parent 1892e0bd9b
commit d57fbde4c0
5 changed files with 126 additions and 101 deletions

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/7e909668316aeda4fb9591ef4393e389.json" "buildInfo": "../../build-info/fa666dddb19de15d02d4eaf695a7b974.json"
} }

File diff suppressed because one or more lines are too long

View File

@ -2,22 +2,24 @@
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract P2PIX { contract P2PIX is Ownable {
event DepositAdded(address indexed seller, bytes32 depositID, address token, uint256 price, uint256 amount); event DepositAdded(address indexed seller, bytes32 depositID, address token, uint256 premium, uint256 amount);
event DepositClosed(address indexed seller, bytes32 depositID); event DepositClosed(address indexed seller, bytes32 depositID);
event DepositWithdrawn(address indexed seller, bytes32 depositID, uint256 amount); 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 LockReleased(address indexed buyer, bytes32 lockId);
event LockReturned(address indexed buyer, bytes32 lockId); event LockReturned(address indexed buyer, bytes32 lockId);
// Events
event PremiumsWithdrawn(address owner, uint256 amount);
struct Deposit { struct Deposit {
address seller; address seller;
address token; // ERC20 stable token address address token; // ERC20 stable token address
uint256 remaining; // Remaining tokens available uint256 remaining; // Remaining tokens available
uint256 price; // Price in R$ per token uint256 premium; // Premium paid in ETH for priority
bool valid; // Could be invalidated by the seller bool valid; // Could be invalidated by the seller
string pixTarget; // The PIX account for the seller receive transactions string pixTarget; // The PIX account for the seller receive transactions
} }
@ -28,7 +30,6 @@ contract P2PIX {
address relayerAddress; // Relayer address that facilitated this transaction address relayerAddress; // Relayer address that facilitated this transaction
uint256 relayerPremium; // Amount to be paid for relayer uint256 relayerPremium; // Amount to be paid for relayer
uint256 amount; // Amount to be tranfered via PIX uint256 amount; // Amount to be tranfered via PIX
uint256 locked; // Amount locked in tokens from deposit
uint256 expirationBlock; // If not paid at this block will be expired uint256 expirationBlock; // If not paid at this block will be expired
} }
@ -49,7 +50,7 @@ contract P2PIX {
_; _;
} }
constructor (uint256 defaultBlocks, address[] memory validSigners) { constructor (uint256 defaultBlocks, address[] memory validSigners) Ownable() {
defaultLockBlocks = defaultBlocks; defaultLockBlocks = defaultBlocks;
for (uint8 i = 0; i < validSigners.length; i++){ for (uint8 i = 0; i < validSigners.length; i++){
validBacenSigners[validSigners[i]] = true; validBacenSigners[validSigners[i]] = true;
@ -60,16 +61,15 @@ contract P2PIX {
function deposit( function deposit(
address token, address token,
uint256 amount, uint256 amount,
uint256 price,
string calldata pixTarget string calldata pixTarget
) public returns (bytes32 depositID){ ) public payable returns (bytes32 depositID){
depositID = keccak256(abi.encodePacked(pixTarget, amount)); depositID = keccak256(abi.encodePacked(pixTarget, amount));
require(!mapDeposits[depositID].valid, 'P2PIX: Deposit already exist and it is still valid'); require(!mapDeposits[depositID].valid, 'P2PIX: Deposit already exist and it is still valid');
IERC20 t = IERC20(token); IERC20 t = IERC20(token);
t.transferFrom(msg.sender, address(this), amount); t.transferFrom(msg.sender, address(this), amount);
Deposit memory d = Deposit(msg.sender, token, amount, price, true, pixTarget); Deposit memory d = Deposit(msg.sender, token, amount, msg.value, true, pixTarget);
mapDeposits[depositID] = d; mapDeposits[depositID] = d;
emit DepositAdded(msg.sender, depositID, token, price, amount); emit DepositAdded(msg.sender, depositID, token, msg.value, amount);
} }
// Vendedor pode invalidar da ordem de venda impedindo novos locks na mesma (isso não afeta nenhum lock que esteja ativo). // Vendedor pode invalidar da ordem de venda impedindo novos locks na mesma (isso não afeta nenhum lock que esteja ativo).
@ -95,8 +95,7 @@ contract P2PIX {
unlockExpired(expiredLocks); unlockExpired(expiredLocks);
Deposit storage d = mapDeposits[depositID]; Deposit storage d = mapDeposits[depositID];
require(d.valid, "P2PIX: Deposit not valid anymore"); require(d.valid, "P2PIX: Deposit not valid anymore");
uint256 toLock = (amount * 1 ether) / d.price; require(d.remaining >= amount, "P2PIX: Not enough token remaining on deposit");
require(d.remaining >= toLock, "P2PIX: Not enough token remaining on deposit");
lockID = keccak256(abi.encodePacked(depositID, amount, targetAddress)); lockID = keccak256(abi.encodePacked(depositID, amount, targetAddress));
require( require(
mapLocks[lockID].expirationBlock < block.number, mapLocks[lockID].expirationBlock < block.number,
@ -108,12 +107,11 @@ contract P2PIX {
relayerAddress, relayerAddress,
relayerPremium, relayerPremium,
amount, amount,
toLock,
block.number+defaultLockBlocks block.number+defaultLockBlocks
); );
mapLocks[lockID] = l; mapLocks[lockID] = l;
d.remaining -= toLock; d.remaining -= amount;
emit LockAdded(targetAddress, lockID, depositID, amount, toLock); emit LockAdded(targetAddress, lockID, depositID, amount);
} }
// Relayer interage com o smart contract, colocando no calldata o comprovante do PIX realizado. // Relayer interage com o smart contract, colocando no calldata o comprovante do PIX realizado.
@ -127,7 +125,7 @@ contract P2PIX {
) public { ) public {
// TODO **Prevenir que um Pix não relacionado ao APP seja usado pois tem o mesmo destino // TODO **Prevenir que um Pix não relacionado ao APP seja usado pois tem o mesmo destino
Lock storage l = mapLocks[lockID]; Lock storage l = mapLocks[lockID];
require(l.expirationBlock > block.number && l.locked > 0, "P2PIX: Lock already released or returned"); require(l.expirationBlock > block.number && l.amount > 0, "P2PIX: Lock already released or returned");
Deposit storage d = mapDeposits[l.depositID]; Deposit storage d = mapDeposits[l.depositID];
bytes32 message = keccak256(abi.encodePacked( bytes32 message = keccak256(abi.encodePacked(
mapDeposits[l.depositID].pixTarget, mapDeposits[l.depositID].pixTarget,
@ -139,29 +137,22 @@ contract P2PIX {
address signer = ecrecover(messageDigest, v, r, s); address signer = ecrecover(messageDigest, v, r, s);
require(validBacenSigners[signer], "P2PIX: Signer is not a valid signer"); require(validBacenSigners[signer], "P2PIX: Signer is not a valid signer");
IERC20 t = IERC20(d.token); IERC20 t = IERC20(d.token);
t.transfer(l.targetAddress, l.locked-l.relayerPremium); t.transfer(l.targetAddress, l.amount-l.relayerPremium);
if (l.relayerPremium > 0) t.transfer(l.relayerAddress, l.relayerPremium); if (l.relayerPremium > 0) t.transfer(l.relayerAddress, l.relayerPremium);
l.locked = 0; l.amount = 0;
l.expirationBlock = 0; l.expirationBlock = 0;
usedTransactions[message] = true; usedTransactions[message] = true;
emit LockReleased(l.targetAddress, lockID); emit LockReleased(l.targetAddress, lockID);
} }
// Change price for deposit amount
function changeDepositPrice(bytes32 depositID, uint256 price) public onlySeller(depositID) {
Deposit storage d = mapDeposits[depositID];
d.price = price;
emit DepositPriceChanged(depositID, price);
}
// Unlock expired locks // Unlock expired locks
function unlockExpired(bytes32[] calldata lockIDs) public { function unlockExpired(bytes32[] calldata lockIDs) public {
uint256 locksSize = lockIDs.length; uint256 locksSize = lockIDs.length;
for (uint16 i = 0; i < locksSize; i++){ for (uint16 i = 0; i < locksSize; i++){
Lock storage l = mapLocks[lockIDs[i]]; Lock storage l = mapLocks[lockIDs[i]];
require(l.expirationBlock < block.number && l.locked > 0, "P2PIX: Lock not expired or already released"); require(l.expirationBlock < block.number && l.amount > 0, "P2PIX: Lock not expired or already released");
mapDeposits[l.depositID].remaining += l.locked; mapDeposits[l.depositID].remaining += l.amount;
l.locked = 0; l.amount = 0;
emit LockReturned(l.targetAddress, lockIDs[i]); emit LockReturned(l.targetAddress, lockIDs[i]);
} }
} }
@ -182,4 +173,11 @@ contract P2PIX {
emit DepositWithdrawn(msg.sender, depositID, amount); emit DepositWithdrawn(msg.sender, depositID, amount);
} }
// O dono do contrato pode sacar os premiums pagos
function withdrawPremiums() external onlyOwner {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
emit PremiumsWithdrawn(msg.sender, balance);
}
} }

View File

@ -40,15 +40,15 @@ describe("P2PIX deposit test", function () {
transaction = await p2pix.deposit( transaction = await p2pix.deposit(
erc20.address, erc20.address,
ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'),
ethers.utils.parseEther('0.99'), 'SELLER PIX KEY',
'SELLER PIX KEY' {value:ethers.utils.parseEther('0.1')}
); );
depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')]) depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')])
await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs( await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs(
owner.address, owner.address,
depositID, depositID,
erc20.address, erc20.address,
ethers.utils.parseEther('0.99'), ethers.utils.parseEther('0.1'),
ethers.utils.parseEther('1000') ethers.utils.parseEther('1000')
) )
}) })
@ -57,8 +57,8 @@ describe("P2PIX deposit test", function () {
await expect(p2pix.deposit( await expect(p2pix.deposit(
erc20.address, erc20.address,
ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'),
ethers.utils.parseEther('0.99'), 'SELLER PIX KEY',
'SELLER PIX KEY' {value:ethers.utils.parseEther('0.1')}
)) ))
.to.be.revertedWith('P2PIX: Deposit already exist and it is still valid'); .to.be.revertedWith('P2PIX: Deposit already exist and it is still valid');
}) })
@ -83,15 +83,15 @@ describe("P2PIX deposit test", function () {
transaction = await p2pix.deposit( transaction = await p2pix.deposit(
erc20.address, erc20.address,
ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'),
ethers.utils.parseEther('0.99'), 'SELLER PIX KEY',
'SELLER PIX KEY' {value:ethers.utils.parseEther('0.1')}
); );
depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')]) depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')])
await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs( await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs(
owner.address, owner.address,
depositID, depositID,
erc20.address, erc20.address,
ethers.utils.parseEther('0.99'), ethers.utils.parseEther('0.1'),
ethers.utils.parseEther('1000') ethers.utils.parseEther('1000')
) )
}) })

View File

@ -41,15 +41,15 @@ describe("P2PIX lock/release test", function () {
transaction = await p2pix.deposit( transaction = await p2pix.deposit(
erc20.address, erc20.address,
ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'),
ethers.utils.parseEther('0.99'), 'SELLER PIX KEY',
'SELLER PIX KEY' {value:ethers.utils.parseEther('0.1')}
); );
depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')]) depositID = ethers.utils.solidityKeccak256(['string', 'uint256'], ['SELLER PIX KEY', ethers.utils.parseEther('1000')])
await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs( await expect(transaction).to.emit(p2pix, 'DepositAdded').withArgs(
owner.address, owner.address,
depositID, depositID,
erc20.address, erc20.address,
ethers.utils.parseEther('0.99'), ethers.utils.parseEther('0.1'),
ethers.utils.parseEther('1000') ethers.utils.parseEther('1000')
) )
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString()) console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
@ -73,8 +73,7 @@ describe("P2PIX lock/release test", function () {
wallet3.address, wallet3.address,
lockID, lockID,
depositID, depositID,
ethers.utils.parseEther('100'), ethers.utils.parseEther('100')
"101010101010101010101"
) )
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString()) console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
}) })
@ -113,7 +112,7 @@ describe("P2PIX lock/release test", function () {
lockID lockID
) )
console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString()) console.log('GAS USED:', (await transaction.wait()).cumulativeGasUsed.toString())
expect(await erc20.balanceOf(wallet3.address)).to.equal("101010101010101010101"); expect(await erc20.balanceOf(wallet3.address)).to.equal(ethers.utils.parseEther('100'));
}) })
it("Should allow recreate same lock", async function () { it("Should allow recreate same lock", async function () {
@ -135,7 +134,6 @@ describe("P2PIX lock/release test", function () {
lockID, lockID,
depositID, depositID,
ethers.utils.parseEther('100'), ethers.utils.parseEther('100'),
"101010101010101010101"
) )
}) })
@ -167,7 +165,7 @@ describe("P2PIX lock/release test", function () {
sig.s, sig.s,
sig.v sig.v
) )
expect(await erc20.balanceOf(wallet3.address)).to.equal("202020202020202020202"); expect(await erc20.balanceOf(wallet3.address)).to.equal(ethers.utils.parseEther('200'));
}) })
it("Should prevent release again the lock", async function () { it("Should prevent release again the lock", async function () {
@ -195,7 +193,7 @@ describe("P2PIX lock/release test", function () {
wallet3.address, wallet3.address,
ethers.constants.AddressZero, ethers.constants.AddressZero,
'0', '0',
ethers.utils.parseEther('800'), ethers.utils.parseEther('900'),
[] []
)).to.be.revertedWith('P2PIX: Not enough token remaining on deposit'); )).to.be.revertedWith('P2PIX: Not enough token remaining on deposit');
}) })
@ -218,8 +216,7 @@ describe("P2PIX lock/release test", function () {
wallet3.address, wallet3.address,
lockID, lockID,
depositID, depositID,
ethers.utils.parseEther('100'), ethers.utils.parseEther('100')
"101010101010101010101"
) )
}) })