Skip to content

Commit 80bea94

Browse files
fix(contract read):Add error handling for community tip token holder selection
- Make community tip settings optional when token holder selection fails - Replace error with warning when community tip settings are missing - Add tests for community oracle error cases - Remove test requiring community tip settings for file uploads
1 parent 855b309 commit 80bea94

File tree

7 files changed

+76
-40
lines changed

7 files changed

+76
-40
lines changed

src/ardrive.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,13 @@ export class ArDrive extends ArDriveAnonymous {
157157
* @remarks Presumes that there's a sufficient wallet balance
158158
*/
159159
async sendCommunityTip({ communityWinstonTip, assertBalance = false }: CommunityTipParams): Promise<TipResult> {
160-
const tokenHolder: ArweaveAddress = await this.communityOracle.selectTokenHolder();
160+
let tokenHolder: ArweaveAddress;
161+
try {
162+
tokenHolder = await this.communityOracle.selectTokenHolder();
163+
} catch (error) {
164+
console.error(`Failed to select token holder: ${error}. Cannot send community tip.`);
165+
throw new Error('Failed to select a token holder to receive the community tip.');
166+
}
161167
const arTransferBaseFee = await this.priceEstimator.getBaseWinstonPriceForByteCount(new ByteCount(0));
162168

163169
const transferResult = await this.walletDao.sendARToAddress(

src/arfs/arfs_cost_calculator.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,16 @@ export class ArFSCostCalculator implements CostCalculator {
7676
const hasFileData = uploadStats.find((u) => u.wrappedEntity.entityType === 'file');
7777
if (hasFileData) {
7878
const communityWinstonTip = await this.communityOracle.getCommunityWinstonTip(winstonPriceOfBundle);
79-
const communityTipTarget = await this.communityOracle.selectTokenHolder();
80-
81-
totalPriceOfBundle = totalPriceOfBundle.plus(communityWinstonTip);
82-
communityTipSettings = { communityTipTarget, communityWinstonTip };
83-
}
79+
let communityTipTarget;
80+
try {
81+
communityTipTarget = await this.communityOracle.selectTokenHolder();
82+
communityTipSettings = { communityTipTarget, communityWinstonTip };
83+
totalPriceOfBundle = totalPriceOfBundle.plus(communityWinstonTip);
84+
} catch (error) {
85+
console.error(`Failed to select token holder: ${error}. Skipping community tip.`);
86+
// Community tip is not added to total price if token holder selection fails
87+
}
88+
}
8489

8590
return {
8691
calculatedBundlePlan: {
@@ -105,17 +110,27 @@ export class ArFSCostCalculator implements CostCalculator {
105110
const winstonPriceOfDataTx = await this.priceEstimator.getBaseWinstonPriceForByteCount(fileDataByteCount);
106111
const winstonPriceOfMetaDataTx = await this.priceEstimator.getBaseWinstonPriceForByteCount(metaDataByteCount);
107112

108-
const communityTipTarget = await this.communityOracle.selectTokenHolder();
109-
const communityWinstonTip = await this.communityOracle.getCommunityWinstonTip(winstonPriceOfDataTx);
113+
let communityTipSettings: CommunityTipSettings | undefined;
114+
let totalPriceOfV2Tx: Winston;
115+
116+
try {
117+
const communityTipTarget = await this.communityOracle.selectTokenHolder();
118+
const communityWinstonTip = await this.communityOracle.getCommunityWinstonTip(winstonPriceOfDataTx);
119+
communityTipSettings = { communityTipTarget, communityWinstonTip };
110120

111-
const totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx)
112-
.plus(this.boostedReward(winstonPriceOfMetaDataTx))
113-
.plus(communityWinstonTip);
121+
totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx)
122+
.plus(this.boostedReward(winstonPriceOfMetaDataTx))
123+
.plus(communityWinstonTip);
124+
} catch (error) {
125+
console.error(`Failed to select token holder: ${error}. Skipping community tip.`);
126+
totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx)
127+
.plus(this.boostedReward(winstonPriceOfMetaDataTx));
128+
}
114129

115130
return {
116131
calculatedFileAndMetaDataPlan: {
117132
uploadStats,
118-
communityTipSettings: { communityTipTarget, communityWinstonTip },
133+
communityTipSettings,
119134
dataTxRewardSettings: this.rewardSettingsForWinston(winstonPriceOfDataTx),
120135
metaDataRewardSettings: this.rewardSettingsForWinston(winstonPriceOfMetaDataTx)
121136
},
@@ -134,15 +149,24 @@ export class ArFSCostCalculator implements CostCalculator {
134149
}> {
135150
const winstonPriceOfDataTx = await this.priceEstimator.getBaseWinstonPriceForByteCount(fileDataByteCount);
136151

137-
const communityTipTarget = await this.communityOracle.selectTokenHolder();
138-
const communityWinstonTip = await this.communityOracle.getCommunityWinstonTip(winstonPriceOfDataTx);
152+
let communityTipSettings: CommunityTipSettings | undefined;
153+
let totalPriceOfV2Tx: Winston;
139154

140-
const totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx).plus(communityWinstonTip);
155+
try {
156+
const communityTipTarget = await this.communityOracle.selectTokenHolder();
157+
const communityWinstonTip = await this.communityOracle.getCommunityWinstonTip(winstonPriceOfDataTx);
158+
communityTipSettings = { communityTipTarget, communityWinstonTip };
159+
160+
totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx).plus(communityWinstonTip);
161+
} catch (error) {
162+
console.error(`Failed to select token holder: ${error}. Skipping community tip.`);
163+
totalPriceOfV2Tx = this.boostedReward(winstonPriceOfDataTx);
164+
}
141165

142166
return {
143167
calculatedFileDataOnlyPlan: {
144168
uploadStats,
145-
communityTipSettings: { communityTipTarget, communityWinstonTip },
169+
communityTipSettings,
146170
dataTxRewardSettings: this.rewardSettingsForWinston(winstonPriceOfDataTx),
147171
metaDataBundleIndex
148172
},

src/arfs/arfsdao.test.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -932,22 +932,6 @@ describe('The ArFSDAO class', () => {
932932
assertFileResult(fileResults[1], 42);
933933
});
934934

935-
it('throws an error if a provided bundle plan has a file entity but no communityTipSettings', async () => {
936-
await expectAsyncErrorThrow({
937-
promiseToError: arfsDao.uploadAllEntities({
938-
bundlePlans: [
939-
{
940-
bundleRewardSettings: { reward: W(20) },
941-
metaDataDataItems: [],
942-
uploadStats: [stubFileUploadStats()]
943-
}
944-
],
945-
v2TxPlans: emptyV2TxPlans
946-
}),
947-
errorMessage: 'Invalid bundle plan, file uploads must include communityTipSettings!'
948-
});
949-
});
950-
951935
it('throws an error if a provided bundle plan has only a single folder entity', async () => {
952936
await expectAsyncErrorThrow({
953937
promiseToError: arfsDao.uploadAllEntities({

src/arfs/arfsdao.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {
12391239
});
12401240
} else {
12411241
if (!communityTipSettings) {
1242-
throw new Error('Invalid bundle plan, file uploads must include communityTipSettings!');
1242+
console.warn('There are no community tip settings for this file upload...');
12431243
}
12441244

12451245
// Prepare file data item and results

src/community/ardrive_community_oracle.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import { fakeArweave, stubCommunityContract } from '../../tests/stubs';
33
import { W } from '../types';
44
import { ArDriveCommunityOracle } from './ardrive_community_oracle';
5+
import { expectAsyncErrorThrow } from '../../tests/test_helpers';
56

67
describe('The ArDriveCommunityOracle', () => {
78
const stubContractReader = {
@@ -10,6 +11,12 @@ describe('The ArDriveCommunityOracle', () => {
1011
}
1112
};
1213

14+
const errorThrowingContractReader = {
15+
async readContract() {
16+
throw new Error('Failed to read contract!');
17+
}
18+
};
19+
1320
describe('getCommunityWinstonTip method', () => {
1421
it('returns the expected community tip result', async () => {
1522
const communityOracle = new ArDriveCommunityOracle(fakeArweave, [stubContractReader]);
@@ -23,6 +30,13 @@ describe('The ArDriveCommunityOracle', () => {
2330

2431
expect(+(await communityOracle.getCommunityWinstonTip(W(10_000_000)))).to.equal(10_000_000);
2532
});
33+
34+
it('returns zero fee when contract reading fails', async () => {
35+
const communityOracle = new ArDriveCommunityOracle(fakeArweave, [errorThrowingContractReader]);
36+
37+
// Should return 0 winston as a fallback
38+
expect(+(await communityOracle.getCommunityWinstonTip(W(100_000_000)))).to.equal(0);
39+
});
2640
});
2741

2842
describe('selectTokenHolder method', () => {
@@ -33,5 +47,14 @@ describe('The ArDriveCommunityOracle', () => {
3347
'abcdefghijklmnopqrxtuvwxyz123456789ABCDEFGH'
3448
);
3549
});
50+
51+
it('throws an error when contract reading fails', async () => {
52+
const communityOracle = new ArDriveCommunityOracle(fakeArweave, [errorThrowingContractReader]);
53+
54+
await expectAsyncErrorThrow({
55+
promiseToError: communityOracle.selectTokenHolder(),
56+
errorMessage: 'Max contract read attempts has been reached on the last fallback contract reader..'
57+
});
58+
});
3659
});
3760
});

src/community/ardrive_community_oracle.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { weightedRandom } from '../utils/common';
22
import { ContractOracle, ContractReader } from './contract_oracle';
33
import { CommunityOracle } from './community_oracle';
4-
import { ArDriveContractOracle, communityTxId } from './ardrive_contract_oracle';
4+
import { ArDriveContractOracle } from './ardrive_contract_oracle';
55
import Arweave from 'arweave';
6-
import { SmartweaveContractReader } from './smartweave_contract_oracle';
76
import { PDSContractCacheServiceContractReader } from './pds_contract_oracle';
87
import { ADDR, ArweaveAddress, W, Winston } from '../types';
8+
import { SmartweaveContractReader } from './smartweave_contract_oracle';
99

1010
/**
1111
* Minimum ArDrive community tip from the Community Improvement Proposal Doc:
@@ -103,9 +103,8 @@ export class ArDriveCommunityOracle implements CommunityOracle {
103103

104104
return ADDR(randomHolder);
105105
} catch (error) {
106-
console.error('Failed to determine token holder: ' + error + '. Using default address.');
107-
// Use ArDrive profit sharing community contract address as default target
108-
return ADDR(communityTxId);
106+
console.error(`Failed to determine token holder: ${error}`);
107+
throw error;
109108
}
110109
}
111110
}

src/types/upload_planner_types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ export interface CalculatedFileAndMetaDataPlan
137137
extends Omit<V2FileAndMetaDataPlan, 'fileDataByteCount' | 'metaDataByteCount'> {
138138
dataTxRewardSettings: RewardSettings;
139139
metaDataRewardSettings: RewardSettings;
140-
communityTipSettings: CommunityTipSettings;
140+
communityTipSettings?: CommunityTipSettings;
141141
}
142142
export interface CalculatedFileDataOnlyPlan extends Omit<V2FileDataOnlyPlan, 'fileDataByteCount'> {
143143
dataTxRewardSettings: RewardSettings;
144-
communityTipSettings: CommunityTipSettings;
144+
communityTipSettings?: CommunityTipSettings;
145145
}
146146
export interface CalculatedFolderMetaDataPlan extends Omit<V2FolderMetaDataPlan, 'metaDataByteCount'> {
147147
metaDataRewardSettings: RewardSettings;

0 commit comments

Comments
 (0)