First commit with contracts and a simple test.
This commit is contained in:
parent
3c30213419
commit
dd4a6fa66e
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
coverage
|
||||||
|
coverage.json
|
||||||
|
typechain
|
||||||
|
typechain-types
|
||||||
|
|
||||||
|
#Hardhat files
|
||||||
|
cache
|
||||||
|
artifacts
|
||||||
|
|
157
contracts/p2pix.sol
Normal file
157
contracts/p2pix.sol
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.9;
|
||||||
|
|
||||||
|
contract P2PIX {
|
||||||
|
|
||||||
|
event DepositAdded(address indexed seller, bytes32 depositID, address token, uint256 amount);
|
||||||
|
event DepositClosed(address indexed seller, bytes32 depositID);
|
||||||
|
event LockAdded(address indexed buyer, bytes32 lockID, uint256 amount);
|
||||||
|
event LockReleased(address indexed buyer, bytes32 lockId);
|
||||||
|
|
||||||
|
struct Deposit {
|
||||||
|
address seller;
|
||||||
|
address token; // ERC20 stable token address
|
||||||
|
uint256 remaining; // Remaining tokens available
|
||||||
|
bool valid; // Could be invalidated by the seller
|
||||||
|
string pixTarget; // The PIX account for the seller receive transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Lock {
|
||||||
|
bytes32 depositID;
|
||||||
|
address targetAddress; // Where goes the tokens when validated
|
||||||
|
address relayerAddress; // Relayer address that facilitated this transaction
|
||||||
|
uint256 relayerPremium; // Amount to be paid for relayer
|
||||||
|
uint256 amount; // Amount to be transfered to buyer
|
||||||
|
uint256 expirationBlock; // IF not paid until this block will be expired
|
||||||
|
bool paid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default blocks that lock will hold tokens
|
||||||
|
uint256 internal defaultLockBlocks;
|
||||||
|
// List of valid Bacen signature addresses
|
||||||
|
mapping(address => bool) public validBacenSigners;
|
||||||
|
|
||||||
|
// Seller list of deposits
|
||||||
|
mapping(bytes32 => Deposit) mapDeposits;
|
||||||
|
mapping(bytes32 => bytes32[]) activeDepositLocks;
|
||||||
|
mapping(bytes32 => uint16) activeLocksPerDeposit;
|
||||||
|
// ***** ESTA PARTE É A MAIS CRÍTICA VISTO QUE É NECESSÁRIO FORMAS DE TRAVAR DEPOSITOS *****
|
||||||
|
// ************ PORÉM SEM A NECESSIDADE DE PERCORRER GRANDES ARRAYS ************************
|
||||||
|
mapping(bytes32 => Lock) mapLocks;
|
||||||
|
mapping(bytes32 => bool) usedTransactions;
|
||||||
|
|
||||||
|
modifier onlySeller(bytes32 depositID) {
|
||||||
|
require(mapDeposits[depositID].seller == msg.sender, "P2PIX: Only seller could call this function.");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (uint256 defaultBlocks, address[] memory validSigners) {
|
||||||
|
defaultLockBlocks = defaultBlocks;
|
||||||
|
for (uint8 i = 0; i < validSigners.length; i++){
|
||||||
|
validBacenSigners[validSigners[i]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendedor precisa mandar token para o smart contract + chave PIX destino. Retorna um DepositID.
|
||||||
|
function deposit(
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
string calldata pixTarget
|
||||||
|
) public returns (bytes32 depositID){
|
||||||
|
// TODO Prevent seller to use same depositID
|
||||||
|
// TODO Transfer tokens to this address
|
||||||
|
Deposit memory d = Deposit(msg.sender, token, amount, true, pixTarget);
|
||||||
|
depositID = keccak256(abi.encodePacked(pixTarget, amount));
|
||||||
|
mapDeposits[depositID] = d;
|
||||||
|
emit DepositAdded(msg.sender, depositID, token, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relayer interage adicionando um “lock” na ordem de venda.
|
||||||
|
// O lock precisa incluir address do comprador + address do relayer + reembolso/premio relayer + valor.
|
||||||
|
// **Só poder ter um lock em aberto para cada (ordem de venda, valor)**.
|
||||||
|
// Só pode fazer lock de ordens que não estão invalidadas(Passo 5).
|
||||||
|
// Essa etapa pode ser feita pelo vendedor conjuntamente com a parte 1.
|
||||||
|
// Retorna um LockID.
|
||||||
|
function lock(
|
||||||
|
bytes32 depositID,
|
||||||
|
address targetAddress,
|
||||||
|
address relayerAddress,
|
||||||
|
uint256 relayerPremium,
|
||||||
|
uint256 amount
|
||||||
|
) public returns (bytes32 lockID){
|
||||||
|
Deposit storage d = mapDeposits[depositID];
|
||||||
|
require(d.valid, "P2PIX: Deposit not valid anymore");
|
||||||
|
require(d.remaining > amount, "P2PIX: Not enough remaining");
|
||||||
|
lockID = keccak256(abi.encodePacked(depositID, amount, targetAddress));
|
||||||
|
require(
|
||||||
|
mapLocks[lockID].expirationBlock < block.number,
|
||||||
|
"P2PIX: Another lock with same ID is not expired yet"
|
||||||
|
);
|
||||||
|
Lock memory l = Lock(depositID, targetAddress, relayerAddress, relayerPremium, amount, block.number+defaultLockBlocks, false);
|
||||||
|
mapLocks[lockID] = l;
|
||||||
|
activeDepositLocks[depositID][activeLocksPerDeposit[depositID]] = lockID;
|
||||||
|
activeLocksPerDeposit[depositID]++;
|
||||||
|
d.remaining -= amount;
|
||||||
|
emit LockAdded(targetAddress, lockID, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relayer interage com o smart contract, colocando no calldata o comprovante do PIX realizado.
|
||||||
|
// Smart contract valida o comprovante, manda os tokens para o endereço do pagador, e reembolsa o custo do gás para o endereço do relayer especificado na parte (2).
|
||||||
|
function release(
|
||||||
|
bytes32 lockID,
|
||||||
|
uint256 pixTimestamp,
|
||||||
|
bytes32 r,
|
||||||
|
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];
|
||||||
|
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.");
|
||||||
|
// TODO Transfer token to target
|
||||||
|
l.paid = true;
|
||||||
|
usedTransactions[message] = true;
|
||||||
|
activeLocksPerDeposit[l.depositID] = unlockExpired(l.depositID);
|
||||||
|
emit LockReleased(l.targetAddress, lockID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock expired locks
|
||||||
|
function unlockExpired(bytes32 depositID) internal returns(uint16 locksLength){
|
||||||
|
bytes32[] storage locks = activeDepositLocks[depositID];
|
||||||
|
uint16 locksPreviousLength = activeLocksPerDeposit[depositID];
|
||||||
|
locksLength = 0;
|
||||||
|
for (uint16 i = 0; i < locksPreviousLength; i++){
|
||||||
|
Lock memory l = mapLocks[locks[i]];
|
||||||
|
if (l.expirationBlock > block.number && !l.paid) {
|
||||||
|
locks[locksLength] = locks[i];
|
||||||
|
locksLength++;
|
||||||
|
} else if (l.expirationBlock < block.number && !l.paid) {
|
||||||
|
mapDeposits[depositID].remaining += l.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locksLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendedor pode invalidar da ordem de venda impedindo novos locks na mesma (isso não afeta nenhum lock que esteja ativo).
|
||||||
|
function cancelDeposit(bytes32 depositID) public onlySeller(depositID) {
|
||||||
|
mapDeposits[depositID].valid = false;
|
||||||
|
emit DepositClosed(mapDeposits[depositID].seller, depositID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Após os locks expirarem, vendedor pode interagir c/ o contrato e recuperar os tokens de um depósito específico.
|
||||||
|
function withdraw(bytes32 depositID) public onlySeller(depositID) {
|
||||||
|
// Unlock expired locks at depositID
|
||||||
|
unlockExpired(depositID);
|
||||||
|
// TODO Transfer remaining tokens back to the seller
|
||||||
|
// Withdraw remaining tokens from mapDeposit[depositID]
|
||||||
|
mapDeposits[depositID].remaining = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
hardhat.config.js
Normal file
6
hardhat.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
require("@nomiclabs/hardhat-waffle");
|
||||||
|
|
||||||
|
/** @type import('hardhat/config').HardhatUserConfig */
|
||||||
|
module.exports = {
|
||||||
|
solidity: "0.8.17",
|
||||||
|
};
|
29702
package-lock.json
generated
Normal file
29702
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "p2pix-smart-contracts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Repository for P2Pix EVM contracts to be imported by the project.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/doiim/p2pix-smart-contracts.git"
|
||||||
|
},
|
||||||
|
"author": "Filipe Soccol (doiim)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/doiim/p2pix-smart-contracts/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/doiim/p2pix-smart-contracts#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
||||||
|
"chai": "^4.3.6",
|
||||||
|
"hardhat": "^2.12.0"
|
||||||
|
}
|
||||||
|
}
|
21
test/1-deposit.test.js
Normal file
21
test/1-deposit.test.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const { expect } = require("chai");
|
||||||
|
const { ethers } = require("hardhat");
|
||||||
|
|
||||||
|
describe("P2PIX deposit test", function () {
|
||||||
|
|
||||||
|
let owner, wallet2, wallet3, wallet4;
|
||||||
|
|
||||||
|
it("Deploy contracts", async function () {
|
||||||
|
|
||||||
|
[owner, wallet2, wallet3, wallet4] = await ethers.getSigners();
|
||||||
|
|
||||||
|
const P2PIX = await ethers.getContractFactory("P2PIX");
|
||||||
|
const p2pix = await P2PIX.deploy(2, [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);
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user