Inteligentna umowa dotycząca umów prawnych: Solidity i Vyper
W 2025 r. Arizona i Wyoming wyraźnie uznały tę skodyfikację umów w inteligentnych kontraktach mogą mieć prawnie wiążącą wartość, jeśli spełniają wymagania tradycyjne aspekty prawa umów: oferta, akceptacja, przyczyna i zdolność stron. Jednakże zasadnicze rozróżnienie pozostaje: jedno inteligentne kontrakty i program, który automatycznie wykonuje wcześniej zdefiniowane warunki; A umowa prawna i porozumienie wiążące pomiędzy stronami. Nie każda inteligentna umowa jest umową prawną i nie każda umowa legalne i zautomatyzowane dzięki inteligentnej umowie.
W tym artykule badamy praktyczne wdrożenie inteligentnych kontraktów do umów naprawdę legalne: automatyczny depozyt, płatność warunkowa, umowy NDA z egzekwowaniem na łańcuchu, oraz skutki prawne w każdym przypadku. Kod znajduje się w Solidity (Ethereum) z porównaniem z Vyperem do zastosowań wymagających wysokiego poziomu bezpieczeństwa.
Czego się nauczysz
- Różnica między inteligentną umową a umową prawną: kiedy działa, a kiedy nie
- Automatyczny wzór depozytu w Solidity
- Warunkowa umowa o płatność (płatność przy odbiorze)
- Vyper dla inteligentnych kontraktów o wysokim bezpieczeństwie: zalety w porównaniu z Solidnością
- Testowanie z Hardhat i Foundry
- Audyty bezpieczeństwa: typowe luki w zabezpieczeniach i sposoby ich uniknięcia
- Opakowanie prawne: łączenie inteligentnych kontraktów z tradycyjnymi kontraktami
Kiedy inteligentny kontrakt ma sens prawny
Inteligentne kontrakty sprawdzają się w scenariuszach, w których:
- Warunki są obiektywnie weryfikowalne w łańcuchu: płatność przychodzi (zdarzenie Blockchain), mija data ważności (znacznik czasu bloku), NFT zostaje przeniesiony.
- Strony nie mają do siebie zaufania i chcą wyeliminować pośrednik (bank, notariusz, agent powierniczy).
- Automatyczne i pożądane wykonanie: żadna impreza nie musi „aktywuj” ręcznie, aby spełnić swoje zobowiązanie.
Inteligentne kontrakty nie nadają się do:
- Umowy zależne od zdarzeń w świecie rzeczywistym, których nie można zweryfikować w łańcuchu (np. „towary dotarły w dobrym stanie”) — wymagają niezawodnych wyroczni.
- Umowy wymagające subiektywnej interpretacji lub negocjacji stron.
- Konteksty, w których jedna ze stron może mieć powód do kwestionowania ważności samego kodu.
Wzór 1: Automatyczny depozyt
Escrow to najbardziej naturalny przypadek użycia legalnych inteligentnych kontraktów: nabywca deponuje środki, które są przekazywane sprzedającemu dopiero po spełnieniu warunku specyfikacja (potwierdzona dostawa, zatwierdzony kamień milowy). W przypadku sporu arbiter decyduje z góry wyznaczony.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title LegalEscrow
* @notice Contratto di escrow per accordi commerciali.
* @dev Collega automaticamente pagamento a consegna verificata.
* Non sostituisce un accordo contrattuale scritto:
* usare come complemento tecnico a un contratto tradizionale.
*/
contract LegalEscrow {
// --- Tipi ---
enum EscrowState { Created, Funded, Delivered, Disputed, Released, Refunded }
struct EscrowAgreement {
address payable buyer;
address payable seller;
address arbiter; // arbitro per le dispute
uint256 amount; // importo in wei
uint256 releaseAfter; // timestamp dopo cui il buyer può forzare il rilascio
EscrowState state;
string legalContractHash; // SHA-256 del contratto legale allegato
string description;
}
// --- Storage ---
mapping(uint256 => EscrowAgreement) public agreements;
uint256 private nextAgreementId;
// --- Events ---
event AgreementCreated(uint256 indexed id, address buyer, address seller, uint256 amount);
event Funded(uint256 indexed id, uint256 amount);
event DeliveryConfirmed(uint256 indexed id, address confirmedBy);
event DisputeRaised(uint256 indexed id, address raisedBy, string reason);
event ArbiterDecision(uint256 indexed id, bool releasedToSeller);
event FundsReleased(uint256 indexed id, address recipient, uint256 amount);
// --- Modificatori ---
modifier onlyBuyer(uint256 id) {
require(msg.sender == agreements[id].buyer, "Solo il buyer può eseguire questa azione");
_;
}
modifier onlySeller(uint256 id) {
require(msg.sender == agreements[id].seller, "Solo il seller può eseguire questa azione");
_;
}
modifier onlyArbiter(uint256 id) {
require(msg.sender == agreements[id].arbiter, "Solo l'arbitro può eseguire questa azione");
_;
}
modifier inState(uint256 id, EscrowState expected) {
require(agreements[id].state == expected, "Operazione non valida nello stato corrente");
_;
}
// --- Funzioni ---
/**
* @notice Crea un nuovo accordo di escrow.
* @param seller Indirizzo del venditore
* @param arbiter Indirizzo dell'arbitro (notaio, avvocato, DAO di arbitrato)
* @param legalContractHash Hash SHA-256 del contratto PDF allegato off-chain
* @param description Descrizione dell'accordo
* @param daysToAutoRelease Giorni dopo i quali il buyer può forzare il rilascio
*/
function createAgreement(
address payable seller,
address arbiter,
string calldata legalContractHash,
string calldata description,
uint256 daysToAutoRelease
) external returns (uint256 agreementId) {
require(seller != address(0) && arbiter != address(0), "Indirizzi non validi");
require(seller != msg.sender, "Buyer e seller non possono coincidere");
agreementId = nextAgreementId++;
agreements[agreementId] = EscrowAgreement({
buyer: payable(msg.sender),
seller: seller,
arbiter: arbiter,
amount: 0,
releaseAfter: block.timestamp + (daysToAutoRelease * 1 days),
state: EscrowState.Created,
legalContractHash: legalContractHash,
description: description
});
emit AgreementCreated(agreementId, msg.sender, seller, 0);
}
/**
* @notice Il buyer finanzia l'escrow inviando ETH.
*/
function fund(uint256 id)
external
payable
onlyBuyer(id)
inState(id, EscrowState.Created)
{
require(msg.value > 0, "Importo deve essere maggiore di zero");
agreements[id].amount = msg.value;
agreements[id].state = EscrowState.Funded;
emit Funded(id, msg.value);
}
/**
* @notice Il buyer conferma la consegna e rilascia i fondi al seller.
*/
function confirmDelivery(uint256 id)
external
onlyBuyer(id)
inState(id, EscrowState.Funded)
{
agreements[id].state = EscrowState.Released;
agreements[id].seller.transfer(agreements[id].amount);
emit DeliveryConfirmed(id, msg.sender);
emit FundsReleased(id, agreements[id].seller, agreements[id].amount);
}
/**
* @notice Il buyer o il seller apre una disputa.
*/
function raiseDispute(uint256 id, string calldata reason)
external
inState(id, EscrowState.Funded)
{
require(
msg.sender == agreements[id].buyer || msg.sender == agreements[id].seller,
"Solo buyer o seller possono aprire una disputa"
);
agreements[id].state = EscrowState.Disputed;
emit DisputeRaised(id, msg.sender, reason);
}
/**
* @notice L'arbitro risolve la disputa.
* @param releaseToSeller true = fondi al seller, false = rimborso al buyer
*/
function resolve(uint256 id, bool releaseToSeller)
external
onlyArbiter(id)
inState(id, EscrowState.Disputed)
{
uint256 amount = agreements[id].amount;
agreements[id].state = releaseToSeller ? EscrowState.Released : EscrowState.Refunded;
if (releaseToSeller) {
agreements[id].seller.transfer(amount);
emit FundsReleased(id, agreements[id].seller, amount);
} else {
agreements[id].buyer.transfer(amount);
emit FundsReleased(id, agreements[id].buyer, amount);
}
emit ArbiterDecision(id, releaseToSeller);
}
}
Wzór 2: Płatność warunkowa z Oracle
Wiele umów prawnych zależy od wydarzeń w świecie rzeczywistym, które nie są bezpośrednio weryfikowalne w łańcuchu: „produkt dotarł do Włoch”, „KPI został osiągnięty”, „patent został zatwierdzony”. The wyrocznia (Łańcuch jest najbardziej rozpowszechniony) wprowadzają dane spoza łańcucha do łańcucha bloków w możliwy do sprawdzenia sposób.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* @title ConditionalPayment
* @notice Pagamento condizionale: si sblocca quando un KPI supera una soglia.
* Esempio: bonus al fornitore quando il tasso di soddisfazione cliente > 4.5/5
*/
contract ConditionalPayment {
address payable public beneficiary; // chi riceve il pagamento
address public payer; // chi ha depositato i fondi
uint256 public threshold; // soglia KPI (in base agli accordi)
uint256 public depositedAmount;
bool public paid;
// Oracle Chainlink per il KPI (es. customer satisfaction score)
AggregatorV3Interface internal kpiOracle;
event PaymentReleased(address to, uint256 amount, int256 kpiValue);
event PaymentRefunded(address to, uint256 amount, int256 kpiValue);
constructor(
address payable _beneficiary,
uint256 _threshold,
address _oracleAddress // indirizzo del price feed / data feed Chainlink
) {
payer = msg.sender;
beneficiary = _beneficiary;
threshold = _threshold;
kpiOracle = AggregatorV3Interface(_oracleAddress);
}
function deposit() external payable {
require(msg.sender == payer, "Solo il payer può depositare");
require(msg.value > 0, "Importo deve essere positivo");
depositedAmount = msg.value;
}
function checkAndPay() external {
require(!paid, "Pagamento già eseguito");
require(depositedAmount > 0, "Nessun fondo depositato");
// Leggi il valore del KPI dall'oracle Chainlink
(, int256 kpiValue, , ,) = kpiOracle.latestRoundData();
paid = true;
if (uint256(kpiValue) >= threshold) {
// KPI raggiunto: paga il beneficiario
beneficiary.transfer(depositedAmount);
emit PaymentReleased(beneficiary, depositedAmount, kpiValue);
} else {
// KPI non raggiunto: rimborsa il payer
payable(payer).transfer(depositedAmount);
emit PaymentRefunded(payer, depositedAmount, kpiValue);
}
}
}
Vyper dla inteligentnych kontraktów o wysokim bezpieczeństwie
Wiele zespołów preferuje transakcje prawne o dużej wartości Vyper w Solidności ze względów bezpieczeństwa. Vyper to język programowania podobny do Pythona dla EVM, który eliminuje z założenia niektóre klasy podatności występujących w Solidity: brak dziedziczenia, bez przeciążania, bez rekurencji, bez dynamicznej modyfikacji tablic.
# @version 0.3.10
# @title SimpleEscrow - Vyper version
# @notice Implementazione Vyper dell'escrow per alta sicurezza.
# Vyper e più verboso ma più leggibile e auditable di Solidity.
# --- Strutture Dati ---
struct Agreement:
buyer: address
seller: address
amount: uint256
released: bool
refunded: bool
disputed: bool
# --- Storage ---
agreements: HashMap[uint256, Agreement]
agreement_count: uint256
owner: address
# --- Events ---
event Funded: indexed(agreement_id: uint256, amount: uint256)
event Released: indexed(agreement_id: uint256)
event Disputed: indexed(agreement_id: uint256)
# --- Costruttore ---
@external
def __init__():
self.owner = msg.sender
self.agreement_count = 0
# --- Funzioni ---
@external
def create_agreement(seller: address) -> uint256:
"""Crea un nuovo accordo di escrow."""
assert seller != msg.sender, "Buyer e seller non possono coincidere"
assert seller != empty(address), "Seller non valido"
agreement_id: uint256 = self.agreement_count
self.agreements[agreement_id] = Agreement({
buyer: msg.sender,
seller: seller,
amount: 0,
released: False,
refunded: False,
disputed: False
})
self.agreement_count += 1
return agreement_id
@external
@payable
def fund(agreement_id: uint256):
"""Il buyer finanzia l'escrow."""
agreement: Agreement = self.agreements[agreement_id]
assert agreement.buyer == msg.sender, "Solo il buyer può finanziare"
assert msg.value > 0, "Importo deve essere positivo"
assert agreement.amount == 0, "Accordo già finanziato"
self.agreements[agreement_id].amount = msg.value
log Funded(agreement_id, msg.value)
@external
def release(agreement_id: uint256):
"""Il buyer rilascia i fondi al seller."""
agreement: Agreement = self.agreements[agreement_id]
assert agreement.buyer == msg.sender, "Solo il buyer può rilasciare"
assert not agreement.released, "Gia rilasciato"
assert not agreement.disputed, "In corso di disputa"
assert agreement.amount > 0, "Nessun fondo depositato"
amount: uint256 = agreement.amount
self.agreements[agreement_id].released = True
self.agreements[agreement_id].amount = 0
send(agreement.seller, amount)
log Released(agreement_id)
Testowanie z Hardhatem
Żadna inteligentna umowa nie powinna zostać wdrożona w środowisku produkcyjnym bez pełnego zestawu testów. Hardhat to standardowe środowisko programistyczne dla inteligentnych kontraktów Ethereum, ze wsparciem dla TypeScriptu i testowania z Ethers.js.
import { ethers } from "hardhat";
import { expect } from "chai";
import { LegalEscrow } from "../typechain-types";
describe("LegalEscrow", function () {
let escrow: LegalEscrow;
let buyer: any, seller: any, arbiter: any, other: any;
beforeEach(async function () {
[buyer, seller, arbiter, other] = await ethers.getSigners();
const EscrowFactory = await ethers.getContractFactory("LegalEscrow");
escrow = await EscrowFactory.deploy() as LegalEscrow;
});
describe("Happy path: buyer conferma consegna", function () {
it("dovrebbe rilasciare i fondi al seller dopo conferma", async function () {
// Crea accordo
const tx = await escrow.connect(buyer).createAgreement(
seller.address, arbiter.address,
"abc123sha256hash", "Fornitura software", 30
);
const receipt = await tx.wait();
const agreementId = 0;
// Buyer finanzia
const amount = ethers.parseEther("1.0");
await escrow.connect(buyer).fund(agreementId, { value: amount });
// Verifica saldo seller prima
const sellerBalanceBefore = await ethers.provider.getBalance(seller.address);
// Buyer conferma consegna
await escrow.connect(buyer).confirmDelivery(agreementId);
// Verifica saldo seller dopo: deve essere aumentato
const sellerBalanceAfter = await ethers.provider.getBalance(seller.address);
expect(sellerBalanceAfter - sellerBalanceBefore).to.equal(amount);
});
});
describe("Dispute path: arbitro risolve a favore buyer", function () {
it("dovrebbe rimborsare il buyer su decisione dell'arbitro", async function () {
const agreementId = 0;
const amount = ethers.parseEther("2.0");
await escrow.connect(buyer).createAgreement(
seller.address, arbiter.address, "hash456", "Consulenza", 30
);
await escrow.connect(buyer).fund(agreementId, { value: amount });
await escrow.connect(buyer).raiseDispute(agreementId, "Deliverable non conforme");
const buyerBalanceBefore = await ethers.provider.getBalance(buyer.address);
await escrow.connect(arbiter).resolve(agreementId, false); // false = rimborso buyer
const buyerBalanceAfter = await ethers.provider.getBalance(buyer.address);
expect(buyerBalanceAfter - buyerBalanceBefore).to.be.closeTo(amount, ethers.parseEther("0.01"));
});
});
describe("Sicurezza: accessi non autorizzati", function () {
it("dovrebbe rigettare confirmDelivery da non-buyer", async function () {
await escrow.connect(buyer).createAgreement(
seller.address, arbiter.address, "hash789", "Test", 30
);
await escrow.connect(buyer).fund(0, { value: ethers.parseEther("1.0") });
await expect(
escrow.connect(other).confirmDelivery(0)
).to.be.revertedWith("Solo il buyer può eseguire questa azione");
});
});
});
Typowe luki w zabezpieczeniach i bezpieczeństwo
Krytyczne luki w prawnych inteligentnych kontraktach
- Ponowne wejście: wzorzec połączenia/przekazu może umożliwić zawarcie umowy złośliwego, aby ponownie wywołać funkcję przed aktualizacją stanu. Zawsze używaj interakcji OpenZeppelin z efektami kontroli i wzorca ReentrancyGuard.
- Przepełnienie/niedomiar liczby całkowitej: w Solidity przed wersją 0.8 było to powszechne; od Operacje arytmetyczne w wersji 0.8 są automatycznie przywracane. Zawsze używaj Solidność 0,8+.
- Manipulacja Oracle: jeżeli umowa zależy od wyroczni, a wyrafinowany atakujący może manipulować danymi Oracle (np. pożyczka flash + cena Oracle). Użyj Chainlink ze średnimi cenami ważonymi w czasie (TWAP).
- Bieganie z przodu: transakcje są widoczne w pierwszej kolejności w pamięci potwierdzenia. W przypadku umów, w których liczy się porządek, użyj schematu zatwierdzenia-ujawnienia.
- Niezmienność błędów: w odróżnieniu od tradycyjnego oprogramowania, błędu w inteligentnej umowie nie można „załatać” po wdrożeniu. Użyj wzorów możliwość aktualizacji serwerów proxy (EIP-1967) lub dodanie mechanizmu zamrażania w sytuacjach awaryjnych.
Opakowanie prawne: połączenie ze światem rzeczywistym
Sama inteligentna umowa nie tworzy zobowiązań prawnych pomiędzy stronami. potrzebny jest A legalne opakowania: tradycyjna umowa, która:
- Zidentyfikuj strony umowy za pomocą prawdziwych danych osobowych
- Określa inteligentny kontrakt pod adresem X i mechanizm wykonania umowy opisanej w umowie
- Określa obowiązującą jurysdykcję i miejsce rozstrzygania sporów
- Zawiera skrót SHA-256 kodu źródłowego kontraktu w celach informacyjnych
- Określa, co się stanie w przypadku błędu lub nieoczekiwanego zachowania inteligentnej umowy
Wnioski
Inteligentne kontrakty dla umów prawnych są potężnym narzędziem, ale wymagają: staranny projekt w celu zidentyfikowania odpowiednich przypadków użycia, kodu napisane z zachowaniem wysokich standardów bezpieczeństwa, wyczerpujące testy, audytowane przez ekspertów ds. bezpieczeństwa blockchain oraz opakowanie prawne, które zakotwicza ich w systemie tradycyjne, prawne.
Przyszłością legalnych inteligentnych kontraktów nie jest zastąpienie tradycyjnych kontraktów, ale ich uzupełnienie: najbardziej złożone umowy handlowe będą miały charakter hybrydowy, z klauzulami wykonywalnymi automatycznie w łańcuchu i zarządzanymi klauzulami interpretacyjnymi przez ludzkich sędziów.
Seria LegalTech i AI
- NLP w analizie kontraktów: od OCR do zrozumienia
- Architektura platformy e-Discovery
- Automatyzacja zgodności z silnikiem dynamicznych reguł
- Inteligentna umowa dotycząca umów prawnych: Solidity i Vyper (ten artykuł)
- Podsumowanie dokumentów prawnych z generatywną sztuczną inteligencją
- Prawo dotyczące wyszukiwarek: osadzanie wektorów
- Podpis cyfrowy i uwierzytelnianie dokumentów w Scala
- Systemy ochrony danych i zgodności z RODO
- Budowanie prawnego asystenta AI (drugi pilot prawniczy)
- Wzór integracji danych LegalTech







