Skip to content

Commit 65bdf00

Browse files
Boxes: Add support for Boxes (#345)
1 parent 684839c commit 65bdf00

32 files changed

+1605
-401
lines changed

src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.algorand.algosdk.builder.transaction;
22

33
import com.algorand.algosdk.crypto.Address;
4+
import com.algorand.algosdk.transaction.AppBoxReference;
45
import com.algorand.algosdk.transaction.Transaction;
56
import com.algorand.algosdk.util.Encoder;
67

@@ -15,6 +16,7 @@ public abstract class ApplicationBaseTransactionBuilder<T extends ApplicationBas
1516
private List<Address> accounts;
1617
private List<Long> foreignApps;
1718
private List<Long> foreignAssets;
19+
private List<AppBoxReference> appBoxReferences;
1820
private Long applicationId;
1921

2022
/**
@@ -36,6 +38,7 @@ protected void applyTo(Transaction txn) {
3638
if (accounts != null) txn.accounts = accounts;
3739
if (foreignApps != null) txn.foreignApps = foreignApps;
3840
if (foreignAssets != null) txn.foreignAssets = foreignAssets;
41+
if (appBoxReferences != null) txn.boxReferences = convertBoxes(appBoxReferences, foreignApps, applicationId);
3942
}
4043

4144
@Override
@@ -63,6 +66,7 @@ public T args(List<byte[]> args) {
6366

6467
/**
6568
* ApplicationArgs lists some transaction-specific arguments accessible from application logic.
69+
*
6670
* @param args List of Base64 encoded strings.
6771
*/
6872
public T argsBase64Encoded(List<String> args) {
@@ -90,4 +94,17 @@ public T foreignAssets(List<Long> foreignAssets) {
9094
this.foreignAssets = foreignAssets;
9195
return (T) this;
9296
}
97+
98+
private List<Transaction.BoxReference> convertBoxes(List<AppBoxReference> abrs, List<Long> foreignApps, Long curApp) {
99+
ArrayList<Transaction.BoxReference> xs = new ArrayList<>();
100+
for (AppBoxReference abr : abrs) {
101+
xs.add(Transaction.BoxReference.fromAppBoxReference(abr, foreignApps, curApp));
102+
}
103+
return xs;
104+
}
105+
106+
public T boxReferences(List<AppBoxReference> boxReferences) {
107+
this.appBoxReferences = boxReferences;
108+
return (T) this;
109+
}
93110
}

src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import java.util.List;
44

55
import com.algorand.algosdk.crypto.Address;
6+
import com.algorand.algosdk.transaction.AppBoxReference;
67

78
public interface ApplicationCallReferencesSetter<T extends ApplicationCallReferencesSetter<T>> {
8-
9+
910
/**
1011
* ApplicationID is the application being interacted with, or 0 if creating a new application.
1112
*/
@@ -27,4 +28,10 @@ public interface ApplicationCallReferencesSetter<T extends ApplicationCallRefere
2728
* application. The access is read-only.
2829
*/
2930
public T foreignAssets(List<Long> foreignAssets);
31+
32+
/**
33+
* BoxReferences lists the boxes whose state may be accessed during evaluation of this application call. The apps
34+
* the boxes belong to must be present in ForeignApps.
35+
*/
36+
public T boxReferences(List<AppBoxReference> boxReferences);
3037
}

src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import com.algorand.algosdk.abi.Method;
44
import com.algorand.algosdk.crypto.Address;
5+
import com.algorand.algosdk.crypto.Digest;
56
import com.algorand.algosdk.crypto.TEALProgram;
67
import com.algorand.algosdk.logic.StateSchema;
8+
import com.algorand.algosdk.transaction.AppBoxReference;
79
import com.algorand.algosdk.transaction.MethodCallParams;
810
import com.algorand.algosdk.transaction.Transaction;
911
import com.algorand.algosdk.transaction.TxnSigner;
1012

13+
import java.math.BigInteger;
1114
import java.util.ArrayList;
1215
import java.util.HashSet;
1316
import java.util.List;
@@ -22,6 +25,7 @@ public class MethodCallTransactionBuilder<T extends MethodCallTransactionBuilder
2225
protected List<Address> foreignAccounts = new ArrayList<>();
2326
protected List<Long> foreignAssets = new ArrayList<>();
2427
protected List<Long> foreignApps = new ArrayList<>();
28+
protected List<AppBoxReference> boxReferences = new ArrayList<>();
2529

2630
protected TEALProgram approvalProgram, clearStateProgram;
2731
protected StateSchema localStateSchema;
@@ -62,7 +66,7 @@ public T method(Method method) {
6266

6367
/**
6468
* Specify arguments for the ABI method invocation.
65-
*
69+
* <p>
6670
* This will reset the arguments list to what is passed in by the caller.
6771
*/
6872
public T methodArguments(List<Object> arguments) {
@@ -72,7 +76,7 @@ public T methodArguments(List<Object> arguments) {
7276

7377
/**
7478
* Specify arguments for the ABI method invocation.
75-
*
79+
* <p>
7680
* This will add the arguments passed in by the caller to the existing list of arguments.
7781
*/
7882
public T addMethodArguments(List<Object> arguments) {
@@ -82,7 +86,7 @@ public T addMethodArguments(List<Object> arguments) {
8286

8387
/**
8488
* Specify arguments for the ABI method invocation.
85-
*
89+
* <p>
8690
* This will add the argument passed in by the caller to the existing list of arguments.
8791
*/
8892
public T addMethodArgument(Object argument) {
@@ -125,6 +129,16 @@ public T foreignAssets(List<Long> foreignAssets) {
125129
return (T) this;
126130
}
127131

132+
@Override
133+
public T boxReferences(List<AppBoxReference> boxReferences) {
134+
if (boxReferences != null)
135+
// duplicate box references can be meaningful, don't get rid of them
136+
this.boxReferences = new ArrayList<>(boxReferences);
137+
else
138+
this.boxReferences.clear();
139+
return (T) this;
140+
}
141+
128142
@Override
129143
public T approvalProgram(TEALProgram approvalProgram) {
130144
this.approvalProgram = approvalProgram;
@@ -162,10 +176,34 @@ public T extraPages(Long extraPages) {
162176
* Build a MethodCallParams object.
163177
*/
164178
public MethodCallParams build() {
165-
return new MethodCallParams(
166-
appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash,
179+
return new MethodCallParamsFactory(appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash,
167180
firstValid, lastValid, fee, flatFee, rekeyTo, signer, foreignAccounts, foreignAssets, foreignApps,
168-
approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages
169-
);
181+
boxReferences, approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages);
182+
}
183+
184+
/**
185+
* MethodCallParamsFactory exists only as a way to facilitate construction of
186+
* `MethodCallParams` instances via a protected constructor.
187+
* <p>
188+
* No extension or other modification is intended.
189+
*/
190+
private static class MethodCallParamsFactory extends MethodCallParams {
191+
192+
MethodCallParamsFactory(Long appID, Method method, List<Object> methodArgs, Address sender,
193+
Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash,
194+
BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee,
195+
Address rekeyTo, TxnSigner signer,
196+
List<Address> fAccounts, List<Long> fAssets, List<Long> fApps, List<AppBoxReference> boxes,
197+
TEALProgram approvalProgram, TEALProgram clearProgram,
198+
StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) {
199+
super(appID, method, methodArgs, sender,
200+
onCompletion, note, lease, genesisID, genesisHash,
201+
firstValid, lastValid, fee, flatFee,
202+
rekeyTo, signer,
203+
fAccounts, fAssets, fApps, boxes,
204+
approvalProgram, clearProgram,
205+
globalStateSchema, localStateSchema, extraPages);
206+
}
207+
170208
}
171209
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.algorand.algosdk.transaction;
2+
3+
import com.algorand.algosdk.util.BoxQueryEncoding;
4+
5+
import java.util.Arrays;
6+
import java.util.Objects;
7+
8+
public class AppBoxReference {
9+
// the app ID of the app this box belongs to. Instead of serializing this value,
10+
// it's used to calculate the appIdx for AppBoxReference.
11+
private final long appId;
12+
13+
// the name of the box unique to the app it belongs to
14+
private final byte[] name;
15+
16+
public AppBoxReference(long appId, byte[] name) {
17+
this.appId = appId;
18+
this.name = Arrays.copyOf(name, name.length);
19+
}
20+
21+
@Override
22+
public boolean equals(Object o) {
23+
if (this == o) return true;
24+
if (o == null || getClass() != o.getClass()) return false;
25+
AppBoxReference that = (AppBoxReference) o;
26+
return appId == that.appId && Arrays.equals(name, that.name);
27+
}
28+
29+
@Override
30+
public int hashCode() {
31+
int result = Objects.hash(appId);
32+
result = 31 * result + Arrays.hashCode(name);
33+
return result;
34+
}
35+
36+
public long getAppId() {
37+
return appId;
38+
}
39+
40+
public byte[] getName() {
41+
return Arrays.copyOf(name, name.length);
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return "AppBoxReference{" +
47+
"appID=" + appId +
48+
", name=" + Arrays.toString(name) +
49+
'}';
50+
}
51+
52+
public String nameQueryEncoded() {
53+
return BoxQueryEncoding.encodeBytes(name);
54+
}
55+
}

src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* MethodCallParams is an object that holds all parameters necessary to invoke {@link AtomicTransactionComposer#addMethodCall(MethodCallParams)}
2323
*/
2424
public class MethodCallParams {
25+
2526
// if the abi type argument number > 15, then the abi types after 14th should be wrapped in a tuple
2627
private static final int MAX_ABI_ARG_TYPE_LEN = 15;
2728

@@ -35,7 +36,8 @@ public class MethodCallParams {
3536
public final List<Address> foreignAccounts;
3637
public final List<Long> foreignAssets;
3738
public final List<Long> foreignApps;
38-
39+
public final List<AppBoxReference> boxReferences;
40+
3941
public final TEALProgram approvalProgram, clearProgram;
4042
public final StateSchema globalStateSchema, localStateSchema;
4143
public final Long extraPages;
@@ -54,17 +56,13 @@ public class MethodCallParams {
5456
public final String genesisID;
5557
public final Digest genesisHash;
5658

57-
/**
58-
* NOTE: it's strongly suggested to use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder}
59-
* instead of this constructor to create a new MethodCallParams object.
60-
*/
61-
public MethodCallParams(Long appID, Method method, List<Object> methodArgs, Address sender,
62-
Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash,
63-
BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee,
64-
Address rekeyTo, TxnSigner signer,
65-
List<Address> fAccounts, List<Long> fAssets, List<Long> fApps,
66-
TEALProgram approvalProgram, TEALProgram clearProgram,
67-
StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) {
59+
protected MethodCallParams(Long appID, Method method, List<Object> methodArgs, Address sender,
60+
Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash,
61+
BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee,
62+
Address rekeyTo, TxnSigner signer,
63+
List<Address> fAccounts, List<Long> fAssets, List<Long> fApps, List<AppBoxReference> boxes,
64+
TEALProgram approvalProgram, TEALProgram clearProgram,
65+
StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) {
6866
if (appID == null || method == null || sender == null || onCompletion == null || signer == null || genesisID == null || genesisHash == null || firstValid == null || lastValid == null || (fee == null && flatFee == null))
6967
throw new IllegalArgumentException("Method call builder error: some required field not added");
7068
if (fee != null && flatFee != null)
@@ -113,16 +111,38 @@ public MethodCallParams(Long appID, Method method, List<Object> methodArgs, Addr
113111
this.foreignAccounts = new ArrayList<>(fAccounts);
114112
this.foreignAssets = new ArrayList<>(fAssets);
115113
this.foreignApps = new ArrayList<>(fApps);
114+
this.boxReferences = new ArrayList<>(boxes);
116115
this.approvalProgram = approvalProgram;
117116
this.clearProgram = clearProgram;
118117
this.globalStateSchema = globalStateSchema;
119118
this.localStateSchema = localStateSchema;
120119
this.extraPages = extraPages;
121120
}
122121

122+
/**
123+
* Deprecated - Use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder}
124+
* to create a new MethodCallParams object instead.
125+
*/
126+
@Deprecated
127+
public MethodCallParams(Long appID, Method method, List<Object> methodArgs, Address sender,
128+
Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash,
129+
BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee,
130+
Address rekeyTo, TxnSigner signer,
131+
List<Address> fAccounts, List<Long> fAssets, List<Long> fApps,
132+
TEALProgram approvalProgram, TEALProgram clearProgram,
133+
StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) {
134+
this(appID, method, methodArgs, sender,
135+
onCompletion, note, lease, genesisID, genesisHash,
136+
firstValid, lastValid, fee, flatFee,
137+
rekeyTo, signer,
138+
fAccounts, fAssets, fApps, new ArrayList(),
139+
approvalProgram, clearProgram,
140+
globalStateSchema, localStateSchema, extraPages);
141+
}
142+
123143
/**
124144
* Create the transactions which will carry out the specified method call.
125-
*
145+
* <p>
126146
* The list of transactions returned by this function will have the same length as method.getTxnCallCount().
127147
*/
128148
public List<TransactionWithSigner> createTransactions() {
@@ -136,6 +156,7 @@ public List<TransactionWithSigner> createTransactions() {
136156
List<Address> foreignAccounts = new ArrayList<>(this.foreignAccounts);
137157
List<Long> foreignAssets = new ArrayList<>(this.foreignAssets);
138158
List<Long> foreignApps = new ArrayList<>(this.foreignApps);
159+
List<AppBoxReference> boxReferences = new ArrayList<>(this.boxReferences);
139160

140161
for (int i = 0; i < this.method.args.size(); i++) {
141162
Method.Arg argT = this.method.args.get(i);
@@ -199,21 +220,22 @@ public List<TransactionWithSigner> createTransactions() {
199220
ApplicationCallTransactionBuilder<?> txBuilder = ApplicationCallTransactionBuilder.Builder();
200221

201222
txBuilder
202-
.firstValid(this.firstValid)
203-
.lastValid(this.lastValid)
204-
.genesisHash(this.genesisHash)
205-
.genesisID(this.genesisID)
206-
.fee(this.fee)
207-
.flatFee(this.flatFee)
208-
.note(this.note)
209-
.lease(this.lease)
210-
.rekey(this.rekeyTo)
211-
.sender(this.sender)
212-
.applicationId(this.appID)
213-
.args(encodedABIArgs)
214-
.accounts(foreignAccounts)
215-
.foreignApps(foreignApps)
216-
.foreignAssets(foreignAssets);
223+
.firstValid(this.firstValid)
224+
.lastValid(this.lastValid)
225+
.genesisHash(this.genesisHash)
226+
.genesisID(this.genesisID)
227+
.fee(this.fee)
228+
.flatFee(this.flatFee)
229+
.note(this.note)
230+
.lease(this.lease)
231+
.rekey(this.rekeyTo)
232+
.sender(this.sender)
233+
.applicationId(this.appID)
234+
.args(encodedABIArgs)
235+
.accounts(foreignAccounts)
236+
.foreignApps(foreignApps)
237+
.foreignAssets(foreignAssets)
238+
.boxReferences(boxReferences);
217239

218240
Transaction tx = txBuilder.build();
219241

@@ -228,7 +250,7 @@ public List<TransactionWithSigner> createTransactions() {
228250
tx.localStateSchema = this.localStateSchema;
229251
if (this.extraPages != null)
230252
tx.extraPages = this.extraPages;
231-
253+
232254
TransactionWithSigner methodCall = new TransactionWithSigner(tx, this.signer);
233255
transactionArgs.add(methodCall);
234256

@@ -245,12 +267,12 @@ private static boolean checkTransactionType(TransactionWithSigner tws, String tx
245267
* and this function will return an index that can be used to reference `objectToBeAdded` in `objectArray`.
246268
*
247269
* @param objectToBeAdded - The value to add to the array. If this value is already present in the array,
248-
* it will not be added again. Instead, the existing index will be returned.
249-
* @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`.
250-
* @param zerothObject - If provided, this value indicated two things: the 0 value is special for this
251-
* array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals
252-
* `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will
253-
* be returned.
270+
* it will not be added again. Instead, the existing index will be returned.
271+
* @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`.
272+
* @param zerothObject - If provided, this value indicated two things: the 0 value is special for this
273+
* array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals
274+
* `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will
275+
* be returned.
254276
* @return An index that can be used to reference `valueToAdd` in `array`.
255277
*/
256278
private static <T> int populateForeignArrayIndex(T objectToBeAdded, List<T> objectArray, T zerothObject) {

0 commit comments

Comments
 (0)