Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XAPI Audit#1 #5

Merged
merged 2 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 162 additions & 32 deletions aggregator/src/abstract/aggregator.abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export enum RequestMethod {
export class PublishChainConfig {
chain_id: ChainId;
xapi_address: string;
reporters_fee: string;
publish_fee: string;
reward_address: string;
version: string;

constructor({ chain_id, xapi_address }: { chain_id: ChainId, xapi_address: string }) {
constructor({ chain_id, xapi_address, reporters_fee, publish_fee, reward_address }: { chain_id: ChainId, xapi_address: string, reporters_fee: string, publish_fee: string, reward_address: string }) {
this.chain_id = chain_id;
this.xapi_address = xapi_address;
this.reporters_fee = reporters_fee;
this.publish_fee = publish_fee;
this.reward_address = reward_address;
this.version = near.blockTimestamp().toString();
}
}

Expand Down Expand Up @@ -87,21 +95,14 @@ class PublishData {
response: Response;
chain_config: PublishChainConfig;
signature: string;
mpc_options: MpcOptions;

nonce: string;
gas_limit: string;
max_fee_per_gas: string;
max_priority_fee_per_gas: string;

constructor({ request_id, response, chain_config, signature, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, response: Response, chain_config: PublishChainConfig, signature: string, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }) {
constructor({ request_id, response, chain_config, signature, mpc_options }: { request_id: RequestId, response: Response, chain_config: PublishChainConfig, signature: string, mpc_options: MpcOptions }) {
this.request_id = request_id;
this.response = response;
this.chain_config = chain_config;
this.signature = signature;
this.nonce = nonce;
this.gas_limit = gas_limit;
this.max_fee_per_gas = max_fee_per_gas;
this.max_priority_fee_per_gas = max_priority_fee_per_gas;
this.mpc_options = mpc_options;
}
}

Expand All @@ -117,6 +118,35 @@ class AggregatedEvent extends Nep297Event {
}
}

class SetPublishChainConfigEvent extends Nep297Event {
constructor(data: PublishChainConfig) {
super("SetPublishChainConfig", data);
}
}

class SyncPublishChainConfigData {
chain_id: ChainId;
xapi_address: string;
version: string;
call_data: Uint8Array;
signature: string;
mpc_options: MpcOptions;
constructor({ chain_id, xapi_address, version, call_data, signature, mpc_options }: { chain_id: ChainId, xapi_address: string, version: string, call_data: Uint8Array, signature: string, mpc_options: MpcOptions }) {
this.chain_id = chain_id;
this.xapi_address = xapi_address;
this.version = version;
this.call_data = call_data;
this.signature = signature;
this.mpc_options = mpc_options;
}
}

class SyncPublishChainConfigEvent extends Nep297Event {
constructor(data: SyncPublishChainConfigData) {
super("SyncPublishChainConfig", data);
}
}

export class ReporterRequired {
quorum: number;
threshold: number;
Expand Down Expand Up @@ -175,6 +205,14 @@ export class MpcConfig {
}
}

export class MpcOptions {
nonce: string
gas_limit: string
max_fee_per_gas: string
max_priority_fee_per_gas: string
key_version: number
}

export class Staked {
amount: string
account_id: AccountId
Expand All @@ -183,6 +221,9 @@ export class Staked {
// Default timeout: 2 hours
const DEFAULT_TIME_OUT = "18000000000000";

// Derivation path prefix for mpc
const DERIVATION_PATH_PREFIX = "XAPI";

export abstract class Aggregator extends ContractBase {
description: string;
latest_request_id: RequestId;
Expand Down Expand Up @@ -268,17 +309,102 @@ export abstract class Aggregator extends ContractBase {
return this.staking_contract;
}

abstract set_publish_chain_config(publis_chain_config: PublishChainConfig): void;
abstract set_publish_chain_config(publish_chain_config: PublishChainConfig): void;
_set_publish_chain_config(publish_chain_config: PublishChainConfig): void {
this._assert_operator();
assert(publish_chain_config.chain_id != null, "chain_id can't be null.");
assert(publish_chain_config.xapi_address != null, "xapi_address can't be null.");
this.publish_chain_config_lookup.set(publish_chain_config.chain_id.toString(), publish_chain_config);
assert(publish_chain_config.reporters_fee != null, "reporters_fee can't be null.");
assert(publish_chain_config.publish_fee != null, "publish_fee can't be null.");
assert(publish_chain_config.reward_address != null, "reward_address can't be null.");
const _publish_config = new PublishChainConfig({ ...publish_chain_config });
this.publish_chain_config_lookup.set(publish_chain_config.chain_id, _publish_config);
new SetPublishChainConfigEvent(_publish_config).emit();
}

abstract sync_publish_config_to_remote({ chain_id, mpc_options }: { chain_id: ChainId, mpc_options: MpcOptions }): NearPromise;
_sync_publish_config_to_remote({ chain_id, mpc_options }: { chain_id: ChainId, mpc_options: MpcOptions }): NearPromise {
this._check_mpc_options(mpc_options);
const _latest_config = this.publish_chain_config_lookup.get(chain_id);
assert(_latest_config != null, `No publish chain config for ${chain_id}`);

const function_call_data = encodeFunctionCall({
functionSignature: "setAggregatorConfig(string,uint256,uint256,address)",
params: [
near.currentAccountId,
BigInt(_latest_config.reporters_fee),
BigInt(_latest_config.publish_fee),
_latest_config.xapi_address
]
})
near.log("functionCallData", function_call_data);

const function_call_data_bytes = hexToBytes(function_call_data);
near.log("bytes functionCallData", Array.from(function_call_data_bytes));

const payload = ethereumTransaction({
chainId: BigInt(chain_id),
nonce: BigInt(mpc_options.nonce),
maxPriorityFeePerGas: BigInt(mpc_options.max_priority_fee_per_gas),
maxFeePerGas: BigInt(mpc_options.max_fee_per_gas),
gasLimit: BigInt(mpc_options.gas_limit),
to: _latest_config.xapi_address,
value: BigInt(0),
data: function_call_data_bytes,
accessList: []
});
const payload_arr = Array.from(payload);
near.log("payload_arr", payload_arr);

const mpc_args = {
"request": {
"key_version": mpc_options.key_version || 0,
"payload": payload_arr,
"path": `${DERIVATION_PATH_PREFIX}-${chain_id}`
}
}
near.log(`before request signature, prepaidGas: ${near.prepaidGas()}, leftGas: ${near.prepaidGas() - near.usedGas()}`)
const promise = NearPromise.new(this.mpc_config.mpc_contract)
// 1 NEAR to request signature, the surplus will be refunded
.functionCall("sign", JSON.stringify(mpc_args), BigInt(this.mpc_config.attached_balance), ONE_TERA_GAS * BigInt(250))
.then(
NearPromise.new(near.currentAccountId())
.functionCall(
"sync_publish_config_to_remote_callback",
JSON.stringify({ chain_id, mpc_options, call_data: function_call_data_bytes, version: _latest_config.version }),
BigInt(0),
// Beware of the 300T cap with mpc gas
BigInt(ONE_TERA_GAS * BigInt(25))
)
);
return promise.asReturn();
}

abstract sync_publish_config_to_remote_callback({ chain_id, mpc_options, call_data, version }: { chain_id: ChainId, mpc_options: MpcOptions, call_data: Uint8Array, version: string }): void;
_sync_publish_config_to_remote_callback({ chain_id, mpc_options, call_data, version }: { chain_id: ChainId, mpc_options: MpcOptions, call_data: Uint8Array, version: string }): void {
const _result = this._promise_result({ promise_index: 0 });
near.log(`sync_publish_config_to_remote_callback ${chain_id}, ${_result.success}, ${_result.result}, version: ${version}`);
const _latest_config = this.publish_chain_config_lookup.get(chain_id);
if (_result.success) {
if (_latest_config.version != version) {
near.log(`Config is out of date, latest: ${_latest_config.version}, want to sync: ${version}`)
return;
}

new SyncPublishChainConfigEvent(new SyncPublishChainConfigData({
chain_id,
mpc_options,
call_data,
version,
signature: _result.result,
xapi_address: _latest_config.xapi_address
})).emit();
}
}

abstract get_publish_chain_config({ chain_id }: { chain_id: ChainId }): PublishChainConfig;
_get_publish_chain_config({ chain_id }: { chain_id: ChainId }): PublishChainConfig {
return this.publish_chain_config_lookup.get(chain_id.toString());
return this.publish_chain_config_lookup.get(chain_id);
}

abstract set_timeout({ timeout }: { timeout: Timestamp }): void;
Expand Down Expand Up @@ -450,12 +576,9 @@ export abstract class Aggregator extends ContractBase {
}
}

abstract publish_external({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): NearPromise;
_publish({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): NearPromise {
assert(nonce != null, "nonce can't be null.");
assert(gas_limit != null, "gas_limit can't be null.");
assert(max_fee_per_gas != null, "max_fee_per_gas can't be null.");
assert(max_priority_fee_per_gas != null, "max_priority_fee_per_gas can't be null.");
abstract publish_external({ request_id, mpc_options }: { request_id: RequestId, mpc_options: MpcOptions }): NearPromise;
_publish({ request_id, mpc_options }: { request_id: RequestId, mpc_options: MpcOptions }): NearPromise {
this._check_mpc_options(mpc_options);

const _response = this.response_lookup.get(request_id);
assert(_response != null, `Response for ${request_id} does not exist`);
Expand All @@ -472,7 +595,7 @@ export abstract class Aggregator extends ContractBase {
}

_response.chain_id = (BigInt(request_id) >> BigInt(192)).toString();
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id.toString());
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id);
assert(_chain_config != null, `Chain config for ${_response.chain_id} does not exist`);

// Relay it https://sepolia.etherscan.io/tx/0xfe2e2e0018f609b5d10250a823f191942fc42d597ad1cccfb4842f43f1d9196e
Expand All @@ -494,10 +617,10 @@ export abstract class Aggregator extends ContractBase {

const payload = ethereumTransaction({
chainId: BigInt(_response.chain_id),
nonce: BigInt(nonce),
maxPriorityFeePerGas: BigInt(max_priority_fee_per_gas),
maxFeePerGas: BigInt(max_fee_per_gas),
gasLimit: BigInt(gas_limit),
nonce: BigInt(mpc_options.nonce),
maxPriorityFeePerGas: BigInt(mpc_options.max_priority_fee_per_gas),
maxFeePerGas: BigInt(mpc_options.max_fee_per_gas),
gasLimit: BigInt(mpc_options.gas_limit),
to: _chain_config.xapi_address,
value: BigInt(0),
data: function_call_data_bytes,
Expand All @@ -509,10 +632,10 @@ export abstract class Aggregator extends ContractBase {

const mpc_args = {
"request": {
"key_version": 0,
"key_version": mpc_options.key_version || 0,
"payload": payload_arr,
// 0x4dd0A89Cb15D953Fc738362066b412fd303BCe17
"path": "test"
"path": `${DERIVATION_PATH_PREFIX}-${_response.chain_id}`
}
}
this.response_lookup.set(request_id, _response);
Expand All @@ -524,7 +647,7 @@ export abstract class Aggregator extends ContractBase {
NearPromise.new(near.currentAccountId())
.functionCall(
"publish_callback",
JSON.stringify({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }),
JSON.stringify({ request_id, mpc_options }),
BigInt(0),
// Beware of the 300T cap with mpc gas
BigInt(ONE_TERA_GAS * BigInt(25))
Expand All @@ -533,22 +656,29 @@ export abstract class Aggregator extends ContractBase {
return promise.asReturn();
}

abstract publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): void
_publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId, nonce: string, gas_limit: string, max_fee_per_gas: string, max_priority_fee_per_gas: string }): void {
abstract publish_callback({ request_id, mpc_options }: { request_id: RequestId, mpc_options: MpcOptions }): void
_publish_callback({ request_id, mpc_options }: { request_id: RequestId, mpc_options: MpcOptions }): void {
const _result = this._promise_result({ promise_index: 0 });
near.log(`publish_callback ${request_id}, ${_result.success}, ${_result.result}`);
const _response = this.response_lookup.get(request_id);
if (_result.success) {
_response.status = RequestStatus[RequestStatus.PUBLISHED];
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id.toString());
const _chain_config = this.publish_chain_config_lookup.get(_response.chain_id);
new PublishEvent(new PublishData({
request_id, response: _response, chain_config: _chain_config, signature: _result.result,
nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas
mpc_options
})).emit();
this.response_lookup.set(request_id, _response);
}
}

private _check_mpc_options(mpc_options: MpcOptions): void {
assert(mpc_options.nonce != null, "nonce can't be null.");
assert(mpc_options.gas_limit != null, "gas_limit can't be null.");
assert(mpc_options.max_fee_per_gas != null, "max_fee_per_gas can't be null.");
assert(mpc_options.max_priority_fee_per_gas != null, "max_priority_fee_per_gas can't be null.");
}

private _report_deposit(report: Report): bigint {
const _bytes = BigInt(sizeOf(report));
// 100KB == 1Near == 10^24 yoctoNear
Expand Down
20 changes: 15 additions & 5 deletions aggregator/src/ormp.aggregator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Find all our documentation at https://docs.near.org
import { NearBindgen, near, call, view, migrate, assert, NearPromise, AccountId } from "near-sdk-js";
import { Aggregator, Answer, ChainId, DataSource, MpcConfig, PublishChainConfig, Report, ReporterRequired, RequestId, Response, Staked, Timestamp } from "./abstract/aggregator.abstract";
import { Aggregator, Answer, ChainId, DataSource, MpcConfig, MpcOptions, PublishChainConfig, Report, ReporterRequired, RequestId, Response, Staked, Timestamp } from "./abstract/aggregator.abstract";
import { ContractSourceMetadata, Standard } from "../../common/src/standard.abstract";

@NearBindgen({})
Expand Down Expand Up @@ -104,13 +104,13 @@ class OrmpAggregator extends Aggregator {
/// Calls

@call({})
publish_external({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId; nonce: string; gas_limit: string; max_fee_per_gas: string; max_priority_fee_per_gas: string; }): NearPromise {
return super._publish({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas });
publish_external({ request_id, mpc_options }: { request_id: RequestId; mpc_options: MpcOptions }): NearPromise {
return super._publish({ request_id, mpc_options });
}

@call({ privateFunction: true })
publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas }: { request_id: RequestId; nonce: string; gas_limit: string; max_fee_per_gas: string; max_priority_fee_per_gas: string; }): void {
super._publish_callback({ request_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas });
publish_callback({ request_id, mpc_options }: { request_id: RequestId; mpc_options: MpcOptions }): void {
super._publish_callback({ request_id, mpc_options });
}

@call({ privateFunction: true })
Expand All @@ -123,6 +123,16 @@ class OrmpAggregator extends Aggregator {
return super._report({ request_id, answers, reward_address });
}

@call({})
sync_publish_config_to_remote({ chain_id, mpc_options }: { chain_id: ChainId; mpc_options: MpcOptions; }): NearPromise {
return super._sync_publish_config_to_remote({ chain_id, mpc_options });
}

@call({ privateFunction: true })
sync_publish_config_to_remote_callback({ chain_id, mpc_options, call_data, version }: { chain_id: ChainId; mpc_options: MpcOptions; call_data: Uint8Array; version: string; }): void {
return super._sync_publish_config_to_remote_callback({ chain_id, mpc_options, call_data, version });
}

@call({ payableFunction: true })
add_data_source(data_source: DataSource): void {
super._add_data_source(data_source);
Expand Down
2 changes: 1 addition & 1 deletion xapi-consumer/contracts/ConsumerExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract ConsumerExample {
xapi = IXAPI(xapiAddress);
}

function makeRequest(string memory aggregator) external payable {
function makeRequest(address aggregator) external payable {
string memory requestData = "{'hello':'world'}";
uint256 requestId = xapi.makeRequest{value: msg.value}(requestData, this.fulfillCallback.selector, aggregator);
emit RequestMade(requestId, requestData);
Expand Down
Loading