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

check accounts and tokens ownership #5

Merged
merged 28 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f1e75f2
check accounts owners as well
bogdan-rosianu Feb 13, 2024
8898546
fix + logging
bogdan-rosianu Feb 13, 2024
dcadb31
test log
bogdan-rosianu Feb 13, 2024
a01b8eb
additional log
bogdan-rosianu Feb 13, 2024
3c9b4d1
logs
bogdan-rosianu Feb 13, 2024
18016c1
logs changes
bogdan-rosianu Feb 13, 2024
c8bc92e
logs changes
bogdan-rosianu Feb 13, 2024
f977dba
revert
bogdan-rosianu Feb 13, 2024
67ac90d
push build
bogdan-rosianu Feb 15, 2024
9e795de
fixes
bogdan-rosianu Feb 15, 2024
88e8d70
fixes
bogdan-rosianu Feb 15, 2024
d976b28
update regex
bogdan-rosianu Feb 15, 2024
66f656c
update re
bogdan-rosianu Feb 15, 2024
cbf248e
fix account extraction
bogdan-rosianu Feb 15, 2024
f5cc0c9
more logs + fix
bogdan-rosianu Feb 15, 2024
3eed3c5
fix
bogdan-rosianu Feb 15, 2024
9e729d1
added token ownership checks
bogdan-rosianu Feb 16, 2024
95e43f3
Merge branch 'main' into check-accounts-owners
bogdan-rosianu Feb 16, 2024
a6862b3
fix build after merge
bogdan-rosianu Feb 16, 2024
6693735
cleanup
bogdan-rosianu Feb 16, 2024
0f97ab2
single account onwer + added default admin wallet
bogdan-rosianu Feb 19, 2024
563f27e
apiUrl refactoring
tanghel Feb 19, 2024
fd5a01d
some more smaller refactorings
tanghel Feb 19, 2024
d326a33
ownerAddress instead of owner
tanghel Feb 19, 2024
8944013
renamings + small refactor
bogdan-rosianu Feb 19, 2024
5716f51
renames and refactorings
tanghel Feb 19, 2024
797863a
return address in case of user wallet
bogdan-rosianu Feb 19, 2024
03b773e
fix tokens regex
bogdan-rosianu Feb 19, 2024
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
155 changes: 128 additions & 27 deletions action/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -187982,6 +187982,7 @@ const robot = (app) => {
app.on(['pull_request.opened', 'pull_request.synchronize'], async (context) => {
try {
const repo = context.repo();
console.log("Starting processing the assets ownership checks");
async function createComment(body) {
try {
await context.octokit.issues.createComment({
Expand All @@ -187996,17 +187997,17 @@ const robot = (app) => {
console.error(error);
}
}
async function getOwners(files) {
async function getIdentityOwners(files) {
const originalOwners = [];
const newOwners = [];
const networkPath = network === 'mainnet' ? '' : `${network}/`;
const infoJsonUrl = `https://raw.githubusercontent.com/multiversx/mx-assets/master/${networkPath}identities/${identity}/info.json`;
const infoJsonUrl = `https://raw.githubusercontent.com/multiversx/mx-assets/master/${networkPath}identities/${asset}/info.json`;
// we try to read the contents of the info.json file
const { data: infoFromMaster } = await axios_1.default.get(infoJsonUrl, { validateStatus: status => [200, 404].includes(status) });
if (infoFromMaster && typeof infoFromMaster === 'object' && infoFromMaster['owners']) {
originalOwners.push(...infoFromMaster.owners);
}
const infoJsonFile = files.find(x => x.filename.endsWith(`/${identity}/info.json`));
const infoJsonFile = files.find(x => x.filename.endsWith(`/${asset}/info.json`));
if (infoJsonFile) {
const { data: infoFromPullRequest } = await axios_1.default.get(infoJsonFile.raw_url);
if (infoFromPullRequest && typeof infoFromPullRequest === 'object' && infoFromPullRequest['owners']) {
Expand All @@ -188025,13 +188026,7 @@ const robot = (app) => {
console.log(`Names of changed files: ${printableFilenames}. original owners=${originalOwners}. new owners: ${newOwners}`);
const allOwners = [];
const allOwnersToCheck = [mainOwner, ...extraOwners];
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 apiUrl = getApiUrl();
for (const owner of allOwnersToCheck) {
if (new out_1.Address(owner).isContractAddress()) {
const ownerResult = await axios_1.default.get(`${apiUrl}/accounts/${owner}?extract=ownerAddress`);
Expand All @@ -188043,18 +188038,65 @@ const robot = (app) => {
}
return [...new Set(allOwners)];
}
async function getAccountOwner(account) {
const accountOwner = account;
if (new out_1.Address(accountOwner).isContractAddress()) {
return getAccountOwnerFromApi(accountOwner);
}
return accountOwner;
}
async function getAccountOwnerFromApi(address) {
const apiUrl = getApiUrl();
const accountOwnerResponse = await axios_1.default.get(`${apiUrl}/accounts/${address}?extract=ownerAddress`);
if (accountOwnerResponse && accountOwnerResponse.data) {
return accountOwnerResponse.data;
}
return '';
}
async function getTokenOwner(token) {
// 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
const apiUrl = getApiUrl();
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 getApiUrl() {
switch (network) {
case 'mainnet':
return 'https://next-api.multiversx.com';
case 'devnet':
return 'https://devnet-api.multiversx.com';
case 'testnet':
return 'https://testnet-api.multiversx.com';
}
throw new Error(`Invalid network: ${network}`);
}
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')) {
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')) {
if (testnetRegex.test(fileName)) {
return 'testnet';
}
if (fileName.startsWith('devnet/identities')) {
if (devnetRegex.test(fileName)) {
return 'devnet';
}
return undefined;
Expand All @@ -188066,6 +188108,20 @@ const robot = (app) => {
.filter(x => x);
return [...new Set(identities)];
}
function getDistinctAccounts(fileNames) {
const regex = /accounts\/(.*?).json/;
const accounts = fileNames
.map(x => regex.exec(x)?.at(1))
.filter(x => x);
return [...new Set(accounts)];
}
function getDistinctTokens(fileNames) {
const regex = /tokens\/(.*?)\/info.json/;
const tokens = fileNames
.map(x => regex.exec(x)?.at(1))
.filter(x => x);
return [...new Set(tokens)];
}
async function fail(reason) {
await createComment(reason);
console.error(reason);
Expand All @@ -188075,7 +188131,7 @@ const robot = (app) => {
const signature = /[0-9a-fA-F]{128}/.exec(body)?.at(0);
if (signature) {
const verifyResult = await verifySignature(signature, address, message);
console.log(`verifying signature for address ${address}, message ${message}, and signature ${signature}. Result=${verifyResult}`);
console.log(`Verifying signature for address ${address}, message ${message}, and signature ${signature}. Result=${verifyResult}`);
return verifyResult;
}
const txHash = /[0-9a-fA-F]{64}/.exec(body)?.at(0);
Expand Down Expand Up @@ -188120,6 +188176,7 @@ const robot = (app) => {
const { data: pullRequest } = await axios_1.default.get(`https://api.github.com/repos/multiversx/mx-assets/pulls/${context.pullRequest().pull_number}`);
const state = pullRequest.state;
if (state === 'closed' || state === 'locked' || state === 'draft') {
await fail(`Invalid PR state: ${state}`);
return 'invalid event payload';
}
const data = await context.octokit.repos.compareCommits({
Expand All @@ -188134,12 +188191,36 @@ const robot = (app) => {
if (!changedFiles?.length) {
return 'no change';
}
const distinctIdentities = getDistinctIdentities(changedFiles.map(x => x.filename));
if (distinctIdentities.length === 0) {
let checkMode = 'identity';
const changedFilesNames = changedFiles.map(x => x.filename);
const distinctStakingIdentities = getDistinctIdentities(changedFilesNames);
const distinctAccounts = getDistinctAccounts(changedFilesNames);
const distinctTokens = getDistinctTokens(changedFilesNames);
const countDistinctStakingIdentities = distinctStakingIdentities.length;
if (countDistinctStakingIdentities) {
checkMode = 'identity';
}
const countDistinctAccounts = distinctAccounts.length;
if (countDistinctAccounts) {
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, ...distinctTokens];
const distinctNetworks = getDistinctNetworks(changedFiles.map(x => x.filename));
if (distinctNetworks.length === 0) {
await fail("No network changed.");
return;
}
const comments = await context.octokit.issues.listComments({
Expand All @@ -188150,13 +188231,14 @@ const robot = (app) => {
});
const body = pullRequest.body || '';
const bodies = [...comments.data.map(x => x.body || ''), body];
const adminAddress = process.env.ADMIN_ADDRESS;
if (adminAddress) {
const invalidAddresses = await multiVerify(bodies, [adminAddress], commitShas);
if (invalidAddresses && invalidAddresses.length === 0) {
await createComment(`Signature OK. Verified that the latest commit hash \`${lastCommitSha}\` was signed using the admin wallet address`);
return;
}
let adminAddress = process.env.ADMIN_ADDRESS;
if (!adminAddress) {
adminAddress = 'erd1cevsw7mq5uvqymjqzwqvpqtdrhckehwfz99n7praty3y7q2j7yps842mqh';
}
const invalidAddressesForAdminChecks = await multiVerify(bodies, [adminAddress], commitShas);
if (invalidAddressesForAdminChecks && invalidAddressesForAdminChecks.length === 0) {
await createComment(`Signature OK. Verified that the latest commit hash \`${lastCommitSha}\` was signed using the admin wallet address`);
return;
}
if (distinctIdentities.length > 1) {
await fail('Only one identity must be edited at a time');
Expand All @@ -188166,14 +188248,33 @@ const robot = (app) => {
await fail('Only one network must be edited at a time');
return;
}
const identity = distinctIdentities[0];
const asset = distinctIdentities[0];
if (!asset) {
await fail('No asset update detected');
return;
}
const network = distinctNetworks[0];
let owners = await getOwners(changedFiles);
let owners;
switch (checkMode) {
case 'identity':
owners = await getIdentityOwners(changedFiles);
break;
case 'account':
const accountOwner = await getAccountOwner(asset);
owners = [accountOwner];
break;
case 'token':
const tokenOwner = await getTokenOwner(asset);
owners = [tokenOwner];
break;
default:
owners = [];
}
if (owners.length === 0) {
await fail('No owners identified');
return;
}
console.log(`Addresses to check ownership for: ${owners}`);
console.log(`Asset owners. check mode=${checkMode}. value=${owners}`);
const invalidAddresses = await multiVerify(bodies, owners, commitShas);
if (!invalidAddresses) {
await fail('Failed to verify owners');
Expand All @@ -188186,7 +188287,7 @@ const robot = (app) => {
return;
}
else {
const ownersDescription = owners.map(address => `\`${address}\``).join('\n');
const ownersDescription = owners.map((address) => `\`${address}\``).join('\n');
await createComment(`Signature OK. Verified that the latest commit hash \`${lastCommitSha}\` was signed using the wallet ${addressDescription}: \n${ownersDescription}`);
}
console.info('successfully reviewed', pullRequest.html_url);
Expand Down
Loading