Skip to content

Commit

Permalink
Merge pull request #41 from pnetwork-association/feat/add-pam
Browse files Browse the repository at this point in the history
Feat/add pam
  • Loading branch information
gitmp01 authored Oct 28, 2024
2 parents 9bff500 + 0d979dd commit 6a71027
Show file tree
Hide file tree
Showing 19 changed files with 834 additions and 216 deletions.
203 changes: 187 additions & 16 deletions cpp/contracts/adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

namespace eosio {

const bytes CHAIN_ID = {
0xac, 0xa3, 0x76, 0xf2, 0x06, 0xb8, 0xfc, 0x25,
0xa6, 0xed, 0x44, 0xdb, 0xdc, 0x66, 0x54, 0x7c,
0x36, 0xc6, 0xc3, 0x3e, 0x3a, 0x11, 0x9f, 0xfb,
0xea, 0xef, 0x94, 0x36, 0x42, 0xf0, 0xe9, 0x06
};

asset adapter::calculate_fees(const asset& quantity) {
registry_adapter _registry(get_self(), get_self().value);
auto idx = _registry.get_index<adapter_registry_idx_xtoken>();
Expand Down Expand Up @@ -46,16 +53,22 @@ void adapter::create(

auto _token_bytes = token_bytes.extract_as_byte_array();
check(_token_bytes.size() == 32, "token bytes length must be 32");
check(is_account(token), "token account does not exist");
check(is_account(xerc20), "xERC20 account does not exist");
check(token_symbol.precision() == xerc20_symbol.precision(), "invalid xerc20 precision");
check(min_fee.symbol == xerc20_symbol, "invalid minimum fee symbol");

registry_adapter _registry(get_self(), get_self().value);
auto itr = _registry.find(token_symbol.code().raw());
check(itr == _registry.end(), "token already registered");
check_symbol_is_valid(xerc20, xerc20_symbol);
check_symbol_is_valid(token, token_symbol);

if (token != name(0)) {
// Check token symbol if toekn is local to a EOS like chain
check_symbol_is_valid(token, token_symbol);
// Difference in precision allowed if token is not local
check(token_symbol.precision() == xerc20_symbol.precision(), "invalid xerc20 precision");
// Do not check token validity if token is not local
check(is_account(token), "token account does not exist");
}

checksum256 c;
_registry.emplace( get_self(), [&]( auto& r ) {
Expand Down Expand Up @@ -95,16 +108,16 @@ void adapter::extract_memo_args(
const vector<string> parts = split(memo, ",");

check(parts.size() == 4, "invalid memo format");
check(is_hex_notation(parts[1]), "chain id must be 0x prefixed");

out_sender = parts[0];
out_dest_chainid = parts[1];
out_dest_chainid = parts[1].substr(2);
out_recipient = parts[2];
uint64_t userdata_id = stoull(parts[3]);

check(out_sender.length() > 0, "invalid sender address");
check(out_recipient.length() > 0, "invalid destination address");
check(is_hex_notation(out_dest_chainid), "chain id must be 0x prefixed");
check(out_dest_chainid.length() == 66, "chain id must be a 32 bytes hex-string");
check(out_dest_chainid.length() == 64, "chain id must be a 32 bytes hex-string");


if (userdata_id > 0) {
Expand Down Expand Up @@ -147,28 +160,93 @@ void adapter::freeuserdata(const name& account) {
}
}

void adapter::settee(public_key pub_key, bytes attestation) {
print("set tee");
require_auth(get_self());
pam::tee_pubkey _tee_pubkey(get_self(), get_self().value);

_tee_pubkey.get_or_create(
get_self(),
pam::tee{.key = public_key()}
);

_tee_pubkey.set(pam::tee{
.key = pub_key
}, get_self());

// print("attestation: ")
}

void adapter::settopiczero(bytes chain_id, bytes topic_zero) {
require_auth(get_self());
check(topic_zero.size() == 32, "expected 32 bytes emitter");
check(chain_id.size() == 32, "expected 32 bytes chain_id");
pam::mappings_table _mappings_table(get_self(), get_self().value);

auto mappings_itr = _mappings_table.find(get_mappings_key(chain_id));

if (mappings_itr == _mappings_table.end()) {
_mappings_table.emplace(get_self(), [&](auto& row) {
row.chain_id = chain_id;
row.topic_zero = topic_zero;
});

print("Added a new mapping for chain_id: ", get_mappings_key(chain_id));
} else {
_mappings_table.modify(mappings_itr, get_self(), [&](auto& row) {
row.topic_zero = topic_zero;
});

print("Updated the topic zero for chain_id: ", get_mappings_key(chain_id));
}
}

void adapter::setemitter(bytes chain_id , bytes emitter) {
print("set emitter");
require_auth(get_self());
check(emitter.size() == 32, "expected 32 bytes emitter");
check(chain_id.size() == 32, "expected 32 bytes chain_id");
pam:: mappings_table _mappings_table(get_self(), get_self().value);

auto mappings_itr = _mappings_table.find(get_mappings_key(chain_id));
if (mappings_itr == _mappings_table.end()) {
_mappings_table.emplace(get_self(), [&](auto& row) {
row.chain_id = chain_id;
row.emitter = emitter;
});

print("Added a new mapping for chain_id: ", get_mappings_key(chain_id));
} else {
_mappings_table.modify(mappings_itr, get_self(), [&](auto& row) {
row.emitter = emitter;
});

print("Updated the emitter for chain_id: ", get_mappings_key(chain_id));
}
}

void adapter::settle(const name& caller, const operation& operation, const metadata& metadata) {
require_auth(caller);

registry_adapter _registry(get_self(), get_self().value);
auto idx_registry = _registry.get_index<adapter_registry_idx_token_bytes>();
auto search_token_bytes = idx_registry.find(operation.token);

check(search_token_bytes != idx_registry.end(), "invalid token");

checksum256 event_id;
check(pam::is_authorized(operation, metadata, event_id), "unauthorized");
checksum256 event_id = sha256((const char*)metadata.preimage.data(), metadata.preimage.size());

pam::check_authorization(get_self(), operation, metadata, event_id);

past_events _past_events(get_self(), get_self().value);
auto idx_past_events = _past_events.get_index<adapter_registry_idx_eventid>();
auto itr = idx_past_events.find(event_id);

// TODO: disabled for tests, enable this when PAM is ready
// check(itr == idx_past_events.end(), "event already processed");
// _past_events.emplace(caller, [&](auto& r) { r.event_id = event_id; });

auto xerc20 = search_token_bytes->xerc20;
check(itr == idx_past_events.end(), "event already processed");
_past_events.emplace(caller, [&](auto& r) { r.event_id = event_id; });

name xerc20 = search_token_bytes->xerc20;
check(is_account(xerc20), "Not valid xerc20 name");
if (operation.amount > 0) {
auto quantity = from_wei(
operation.amount,
Expand Down Expand Up @@ -199,7 +277,6 @@ void adapter::settle(const name& caller, const operation& operation, const metad
}
}


void adapter::swap(const uint64_t& nonce, const bytes& event_bytes) {
require_auth(get_self());

Expand All @@ -211,7 +288,6 @@ void adapter::swap(const uint64_t& nonce, const bytes& event_bytes) {
printhex(event_bytes.data(), event_bytes.size());
}


void adapter::token_transfer_from_lockbox(
const name& self,
const name& token,
Expand Down Expand Up @@ -334,5 +410,100 @@ void adapter::onmint(const name& caller, const name& to, const asset& quantity,
ontransfer(caller, to, quantity, memo);
}

} // namespace eosio
bool pam::context_checks(const operation& operation, const metadata& metadata) {
uint8_t offset = 2; // Skip protocol, version
bytes origin_chain_id = extract_32bytes(metadata.preimage, offset);

if (origin_chain_id != operation.originChainId) {
return false;
}

offset += 32;
bytes block_id = extract_32bytes(metadata.preimage, offset);

offset += 32;
bytes tx_id = extract_32bytes(metadata.preimage, offset);

if (block_id != operation.blockId || tx_id != operation.txId) {
return false;
}

return true;
}

void pam::check_authorization(name adapter, const operation& operation, const metadata& metadata, checksum256 event_id) {
check(context_checks(operation, metadata), "unexpected context");

pam::tee_pubkey _tee_pubkey(adapter, adapter.value);
public_key tee_key = _tee_pubkey.get().key;

uint128_t offset = 2;
bytes origin_chain_id = extract_32bytes(metadata.preimage, offset);
mappings_table _mappings_table(adapter, adapter.value);
auto itr_mappings = _mappings_table.find(get_mappings_key(origin_chain_id));
check(itr_mappings != _mappings_table.end(), "origin chain_id not registered");
bytes exp_emitter = itr_mappings->emitter;
bytes exp_topic_zero = itr_mappings->topic_zero;

signature sig = convert_bytes_to_signature(metadata.signature);
public_key recovered_pubkey = recover_key(event_id, sig);
check(recovered_pubkey == tee_key, "Invalid signature");

offset = 0;
bytes event_payload(metadata.preimage.begin() + 98, metadata.preimage.end());
bytes emitter = extract_32bytes(event_payload, offset);
check(emitter == exp_emitter && !is_all_zeros(emitter), "unexpected Emitter");
offset += 32;

bytes topic_zero = extract_32bytes(event_payload, offset);
check(topic_zero == exp_topic_zero && !is_all_zeros(topic_zero), "unexpected Topic Zero");
offset += 32 * 3; // skip other topics

// check nonce
bytes event_data(event_payload.begin() + offset, event_payload.end());
bytes nonce = extract_32bytes(event_data, offset);
uint64_t nonce_int = bytes32_to_uint64(nonce);
check(operation.nonce == nonce_int, "nonce do not match");
offset += 32;

// check origin token
bytes token = extract_32bytes(event_data, offset);
checksum256 token_hash = bytes32_to_checksum256(token);
check(operation.token == token_hash, "token adddress do not match");
offset += 32;

// check destination chain id
bytes dest_chain_id = extract_32bytes(event_data, offset);
check(operation.destinationChainId == dest_chain_id, "destination chain Id does not match with the expected one");
check(CHAIN_ID == dest_chain_id, "destination chain Id does not match with the current chain");
offset += 32;

// check amount
bytes amount = extract_32bytes(event_data, offset);
uint128_t amount_num = bytes32_to_uint128(amount);
check(operation.amount == amount_num, "amount do not match");
offset += 32;

// check sender address
bytes sender = extract_32bytes(event_data, offset);
check(operation.sender == sender, "sender do not match");
offset += 32;

// check recipient address
bytes recipient_len = extract_32bytes(event_data, offset);
offset += 32;
uint128_t recipient_len_num = bytes32_to_uint128(recipient_len);
const uint128_t UINT128_MAX = (uint128_t)-1;
check(recipient_len_num <= UINT128_MAX - offset, "overflow detected in data field");
bytes recipient(event_data.begin() + offset, event_data.begin() + offset + recipient_len_num);
name recipient_name = bytes_to_name(recipient);
check(operation.recipient == recipient_name, "recipient do not match");
offset += recipient_len_num;

bytes user_data(event_data.begin() + offset, event_data.end());
checksum256 data256 = sha256((const char*)user_data.data(), user_data.size());
checksum256 op_data256 = sha256((const char*)operation.data.data(), operation.data.size());
check(data256 == op_data256, "user data do not match");
}

} // namespace eosio
28 changes: 14 additions & 14 deletions cpp/contracts/adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ namespace eosio {
public:
using contract::contract;

[[eosio::action]]
void create(
ACTION create(
const name& xerc20,
const symbol& xerc20_symbol,
const name& token,
Expand All @@ -40,20 +39,21 @@ namespace eosio {
const asset& min_fee
);

[[eosio::action]]
void setfeemanagr(const name& fee_manager);
ACTION setfeemanagr(const name& fee_manager);

[[eosio::action]]
void adduserdata(const name& caller, bytes payload);
ACTION adduserdata(const name& caller, bytes payload);

[[eosio::action]]
void freeuserdata(const name& account);
ACTION freeuserdata(const name& account);

[[eosio::action]]
void swap(const uint64_t& nonce, const bytes& event_bytes);
ACTION settee(public_key pub_key, bytes attestation);

[[eosio::action]]
void settle(const name& caller, const operation& operation, const metadata& metadata);
ACTION settopiczero(bytes chain_id, bytes topic_zero);

ACTION setemitter(bytes chain_id, bytes emitter);

ACTION swap(const uint64_t& nonce, const bytes& event_bytes);

ACTION settle(const name& caller, const operation& operation, const metadata& metadata);

[[eosio::on_notify("*::mint")]]
void onmint(const name& caller, const name& to, const asset& quantity, const string& memo);
Expand All @@ -71,13 +71,13 @@ namespace eosio {
uint128_t FEE_BASIS_POINTS = 1750;
uint128_t FEE_BASIS_POINTS_DIVISOR = 1000000; // 4 decimals for basis point + 2 decimals for percentage

struct [[eosio::table]] global_storage_table {
TABLE global_storage_table {
uint128_t nonce;
name feesmanager;
};

// Scoped with user account
struct [[eosio::table]] user_data_table {
TABLE user_data_table {
uint64_t id;
bytes payload;

Expand Down
39 changes: 32 additions & 7 deletions cpp/contracts/pam.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
#pragma once

#include "operation.hpp"
#include "metadata.hpp"
#include "operation.hpp"
#include <eosio/crypto.hpp>
#include <eosio/singleton.hpp>

namespace eosio {
using bytes = std::vector<uint8_t>;
namespace pam {
bool is_authorized(const operation& _operation, const metadata& _metadata, const checksum256& out_event_id) {
// TODO: not implemented
return true;
}
using bytes = std::vector<uint8_t>;
namespace pam {
TABLE mappings {
bytes chain_id;
bytes emitter;
bytes topic_zero;

uint64_t primary_key() const {
eosio::check(chain_id.size() == 32, "Chain ID must be 32 bytes long.");
return (static_cast<uint64_t>(chain_id[24]) << 56) |
(static_cast<uint64_t>(chain_id[25]) << 48) |
(static_cast<uint64_t>(chain_id[26]) << 40) |
(static_cast<uint64_t>(chain_id[27]) << 32) |
(static_cast<uint64_t>(chain_id[28]) << 24) |
(static_cast<uint64_t>(chain_id[29]) << 16) |
(static_cast<uint64_t>(chain_id[30]) << 8) |
(static_cast<uint64_t>(chain_id[31]));
}
};

TABLE tee {
public_key key;
};

using tee_pubkey = singleton<"tee"_n, tee>;
typedef eosio::multi_index<"mappings"_n, mappings> mappings_table;

bool context_checks(const operation& operation, const metadata& metadata);
void check_authorization(name adapter, const operation& operation, const metadata& metadata, checksum256 event_id);
};
}
2 changes: 1 addition & 1 deletion cpp/contracts/tables/adapter_past_events.table.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <eosio/eosio.hpp>

namespace eosio {
struct [[eosio::table]] adapter_past_events_table {
TABLE adapter_past_events_table {
uint64_t notused;
checksum256 event_id;

Expand Down
Loading

0 comments on commit 6a71027

Please sign in to comment.