Skip to content

Commit

Permalink
added token ownership checks
Browse files Browse the repository at this point in the history
  • Loading branch information
bogdan-rosianu committed Feb 16, 2024
1 parent 3eed3c5 commit 9e729d1
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 34 deletions.
88 changes: 71 additions & 17 deletions action/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -188079,18 +188079,49 @@ const robot = (app) => {
}
return [...new Set(allOwners)];
}
async function getTokenOwner() {
// since the token owner can be changed at protocol level at any time, it's enough to check the ownership of the token,
// without checking any previous owners
if (!identity) {
return '';
}
const token = identity;
let apiUrl = 'https://next-api.multiversx.com';
if (network === 'devnet') {
apiUrl = 'https://devnet-api.multiversx.com';
}
else if (network === 'testnet') {
apiUrl = 'https://testnet-api.multiversx.com';
}
const tokenOwner = await getTokenOwnerFromApi(token, apiUrl);
if (new out_1.Address(tokenOwner).isContractAddress()) {
const ownerResult = await axios_1.default.get(`${apiUrl}/tokens/${token}?extract=ownerAddress`);
return ownerResult.data;
}
return tokenOwner;
}
async function getTokenOwnerFromApi(token, apiUrl) {
const tokenOwnerResponse = await axios_1.default.get(`${apiUrl}/tokens/${token}?extract=owner`);
if (tokenOwnerResponse && tokenOwnerResponse.data) {
return tokenOwnerResponse.data;
}
return '';
}
function getDistinctNetworks(fileNames) {
const networks = fileNames.map(fileName => getNetwork(fileName)).filter(x => x !== undefined);
return [...new Set(networks)];
}
function getNetwork(fileName) {
if (fileName.startsWith('identities') || fileName.startsWith('accounts')) {
const mainnetRegex = /^(identities|accounts|tokens)\b/;
const testnetRegex = /^testnet\/(identities|accounts|tokens)\b/;
const devnetRegex = /^devnet\/(identities|accounts|tokens)\b/;
if (mainnetRegex.test(fileName)) {
return 'mainnet';
}
if (fileName.startsWith('testnet/identities') || fileName.startsWith('testnet/accounts')) {
if (testnetRegex.test(fileName)) {
return 'testnet';
}
if (fileName.startsWith('devnet/identities') || fileName.startsWith('devnet/accounts')) {
if (devnetRegex.test(fileName)) {
return 'devnet';
}
return undefined;
Expand All @@ -188109,6 +188140,13 @@ const robot = (app) => {
.filter(x => x);
return [...new Set(accounts)];
}
function getDistinctTokens(fileNames) {
const regex = /tokens\/(.*?).json/;
const accounts = fileNames
.map(x => regex.exec(x)?.at(1))
.filter(x => x);
return [...new Set(accounts)];
}
async function fail(reason) {
await createComment(reason);
console.error(reason);
Expand Down Expand Up @@ -188181,20 +188219,29 @@ const robot = (app) => {
console.log({ changedFiles });
const distinctStakingIdentities = getDistinctIdentities(changedFilesNames);
const distinctAccounts = getDistinctAccounts(changedFilesNames);
const distinctTokens = getDistinctTokens(changedFilesNames);
const countDistinctStakingIdentities = distinctStakingIdentities.length;
const countDistinctAccounts = distinctAccounts.length;
if (countDistinctStakingIdentities === 0 && countDistinctAccounts === 0) {
await fail("No identity or account changed.");
return;
if (countDistinctStakingIdentities) {
checkMode = 'identity';
}
const countDistinctAccounts = distinctAccounts.length;
if (countDistinctAccounts) {
if (countDistinctStakingIdentities) {
await fail("Only one identity or account update at a time.");
return;
}
checkMode = 'account';
}
const distinctIdentities = [...distinctStakingIdentities, ...distinctAccounts];
const countDistinctTokens = distinctTokens.length;
if (countDistinctTokens) {
checkMode = 'token';
}
const sumOfAllChangedAssets = countDistinctAccounts + countDistinctStakingIdentities + countDistinctTokens;
if (sumOfAllChangedAssets === 0) {
await fail("No identity, token or account changed.");
return;
}
if (sumOfAllChangedAssets > 1) {
await fail("Only one identity, token or account update at a time.");
return;
}
const distinctIdentities = [...distinctStakingIdentities, ...distinctAccounts, ...distinctTokens];
const distinctNetworks = getDistinctNetworks(changedFiles.map(x => x.filename));
if (distinctNetworks.length === 0) {
await fail("No network changed.");
Expand Down Expand Up @@ -188230,11 +188277,18 @@ const robot = (app) => {
const identity = distinctIdentities[0];
const network = distinctNetworks[0];
let owners;
if (checkMode == 'identity') {
owners = await getIdentityOwners(changedFiles);
}
else {
owners = await getAccountOwner(changedFiles);
switch (checkMode) {
case 'identity':
owners = await getIdentityOwners(changedFiles);
break;
case 'account':
owners = await getAccountOwner(changedFiles);
break;
case 'token':
owners = [...await getTokenOwner()];
break;
default:
owners = [];
}
if (owners.length === 0) {
await fail('No owners identified');
Expand Down
101 changes: 84 additions & 17 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,67 @@ export const robot = (app: Probot) => {
return [...new Set(allOwners)];
}

async function getTokenOwner(): Promise<string> {
// since the token owner can be changed at protocol level at any time, it's enough to check the ownership of the token,
// without checking any previous owners
if (!identity) {
return '';
}
const token = identity;

let apiUrl = 'https://next-api.multiversx.com';
if (network === 'devnet') {
apiUrl = 'https://devnet-api.multiversx.com';
} else if (network === 'testnet') {
apiUrl = 'https://testnet-api.multiversx.com';
}


const tokenOwner = await getTokenOwnerFromApi(token, apiUrl);
if (new Address(tokenOwner).isContractAddress()) {
const ownerResult = await axios.get(`${apiUrl}/tokens/${token}?extract=ownerAddress`);
return ownerResult.data;
}

return tokenOwner;
}

async function getTokenOwnerFromApi(token: string, apiUrl: string): Promise<string> {
const tokenOwnerResponse = await axios.get(`${apiUrl}/tokens/${token}?extract=owner`);
if (tokenOwnerResponse && tokenOwnerResponse.data) {
return tokenOwnerResponse.data;
}

return '';
}

function getDistinctNetworks(fileNames: string[]) {
const networks = fileNames.map(fileName => getNetwork(fileName)).filter(x => x !== undefined);

return [...new Set(networks)];
}

function getNetwork(fileName: string): 'mainnet' | 'devnet' | 'testnet' | undefined {
if (fileName.startsWith('identities') || fileName.startsWith('accounts')) {
const mainnetRegex = /^(identities|accounts|tokens)\b/;
const testnetRegex = /^testnet\/(identities|accounts|tokens)\b/;
const devnetRegex = /^devnet\/(identities|accounts|tokens)\b/;

if (mainnetRegex.test(fileName)) {
return 'mainnet';
}

if (fileName.startsWith('testnet/identities') || fileName.startsWith('testnet/accounts')) {
if (testnetRegex.test(fileName)) {
return 'testnet';
}

if (fileName.startsWith('devnet/identities') || fileName.startsWith('devnet/accounts')) {
if (devnetRegex.test(fileName)) {
return 'devnet';
}

return undefined;
}


function getDistinctIdentities(fileNames: string[]) {
const regex = /identities\/(.*?)\//;

Expand All @@ -163,6 +202,16 @@ export const robot = (app: Probot) => {
return [...new Set(accounts)];
}

function getDistinctTokens(fileNames: string[]) {
const regex = /tokens\/(.*?).json/;

const accounts = fileNames
.map(x => regex.exec(x)?.at(1))
.filter(x => x);

return [...new Set(accounts)];
}

async function fail(reason: string) {
await createComment(reason);
console.error(reason);
Expand Down Expand Up @@ -255,23 +304,32 @@ export const robot = (app: Probot) => {
console.log({ changedFiles });
const distinctStakingIdentities = getDistinctIdentities(changedFilesNames);
const distinctAccounts = getDistinctAccounts(changedFilesNames);
const distinctTokens = getDistinctTokens(changedFilesNames);

const countDistinctStakingIdentities = distinctStakingIdentities.length;
const countDistinctAccounts = distinctAccounts.length;
if (countDistinctStakingIdentities === 0 && countDistinctAccounts === 0) {
await fail("No identity or account changed.");
return;
if (countDistinctStakingIdentities) {
checkMode = 'identity';
}

const countDistinctAccounts = distinctAccounts.length;
if (countDistinctAccounts) {
if (countDistinctStakingIdentities) {
await fail("Only one identity or account update at a time.");
return;
}
checkMode = 'account';
}
const countDistinctTokens = distinctTokens.length;
if (countDistinctTokens) {
checkMode = 'token';
}

const sumOfAllChangedAssets = countDistinctAccounts + countDistinctStakingIdentities + countDistinctTokens;
if (sumOfAllChangedAssets === 0) {
await fail("No identity, token or account changed.");
return;
}
if (sumOfAllChangedAssets > 1) {
await fail("Only one identity, token or account update at a time.");
return;
}

const distinctIdentities = [...distinctStakingIdentities, ...distinctAccounts];
const distinctIdentities = [...distinctStakingIdentities, ...distinctAccounts, ...distinctTokens];

const distinctNetworks = getDistinctNetworks(changedFiles.map(x => x.filename));
if (distinctNetworks.length === 0) {
Expand Down Expand Up @@ -317,11 +375,20 @@ export const robot = (app: Probot) => {
const network = distinctNetworks[0];

let owners: string[];
if (checkMode == 'identity') {
owners = await getIdentityOwners(changedFiles);
} else {
owners = await getAccountOwner(changedFiles);
switch (checkMode) {
case 'identity':
owners = await getIdentityOwners(changedFiles);
break;
case 'account':
owners = await getAccountOwner(changedFiles);
break;
case 'token':
owners = [...await getTokenOwner()];
break;
default:
owners = [];
}

if (owners.length === 0) {
await fail('No owners identified');
return;
Expand Down

0 comments on commit 9e729d1

Please sign in to comment.