diff --git a/convex-core/src/main/cvx/convex/asset/fungible.cvx b/convex-core/src/main/cvx/convex/asset/fungible.cvx index 7013be93f..a3807d611 100644 --- a/convex-core/src/main/cvx/convex/asset/fungible.cvx +++ b/convex-core/src/main/cvx/convex/asset/fungible.cvx @@ -42,8 +42,12 @@ (cond (> amount bal) (fail :FUNDS "Burn amount not available" )) ;; do updates to balance and supply - (set-holding *caller* (- bal amount)) - (set! supply (- supply amount)))) + (let [new-supply (- supply amount)] + (log "MINT" *caller* (- amount) new-supply) + (set-holding *caller* (- bal amount)) + (set! supply new-supply) + ) + )) (defn mint ^{:callable true} @@ -57,8 +61,11 @@ ;; New supply must be in valid range. ~(if max-supply `(when-not (<= 0 new-supply max-supply) (fail :STATE "Mint exceeds max supply"))) + (log "MINT" *caller* amount new-supply) (set-holding *caller* new-bal) - (set! supply new-supply)))))) + (set! supply new-supply) + + ))))) (defn build-token ^{:doc {:description ["Creates deployable code for a new fungible token which follows the interface described in `convex.asset`." @@ -164,10 +171,17 @@ (cond (> amount bal) (fail :FUNDS "insufficent token balance")) ;; Need this in case of self-transfers. - (when (= *caller* addr) (return amount)) + (when (= *caller* addr) + (log "TR" *caller* addr amount bal bal data) + (return amount)) + - (set-holding *caller* (- bal amount)) - (set-holding addr (+ tbal amount)))) + (let [nsb (- bal amount) + nrb (+ tbal amount)] + (log "TR" *caller* addr amount nsb nrb data) + (set-holding *caller* nsb) + (set-holding addr nrb)) + )) (defn get-offer ^{:callable true} diff --git a/convex-core/src/main/cvx/convex/asset/multi-token.cvx b/convex-core/src/main/cvx/convex/asset/multi-token.cvx index d838237b3..4696d507e 100644 --- a/convex-core/src/main/cvx/convex/asset/multi-token.cvx +++ b/convex-core/src/main/cvx/convex/asset/multi-token.cvx @@ -88,9 +88,15 @@ ;; Need this check in case of self-transfers. (when (= *caller* addr) + (log "TR" addr addr amount bal bal data) (return amount)) - (-set-balance *caller* id (- bal amount)) - (-set-balance addr id (+ tbal amount)))) + + (let [nsb (- bal amount) + nrb (+ tbal amount)] + (log "TR" *caller* addr amount nsb nrb data) + (-set-balance *caller* id nsb) + (-set-balance addr id nrb)) + )) (defn balance ^{:callable true} diff --git a/convex-core/src/main/cvx/convex/asset/share.cvx b/convex-core/src/main/cvx/convex/asset/share.cvx index 5c2758227..0fa6030bf 100644 --- a/convex-core/src/main/cvx/convex/asset/share.cvx +++ b/convex-core/src/main/cvx/convex/asset/share.cvx @@ -102,11 +102,14 @@ ;; Need this check in case of self-transfers. (when (= *caller* addr) + (log "TR" addr addr amount bal bal data) (return amount)) - ;; TODO: need to transfer unclaimed quantity - (-set-balance *caller* id (- bal amount)) - (-set-balance addr id (+ tbal amount)) + (let [nsb (- bal amount) + nrb (+ tbal amount)] + (log "TR" *caller* addr amount nsb nrb data) + (-set-balance *caller* id nsb) + (-set-balance addr id nrb)) amount)) (defn balance diff --git a/convex-core/src/main/cvx/convex/asset/wrap/convex.cvx b/convex-core/src/main/cvx/convex/asset/wrap/convex.cvx index 7f6b3b02c..3e33d726f 100644 --- a/convex-core/src/main/cvx/convex/asset/wrap/convex.cvx +++ b/convex-core/src/main/cvx/convex/asset/wrap/convex.cvx @@ -49,11 +49,17 @@ (cond (< amount 0) (fail :ARGUMENT "negative transfer")) (cond (> amount bal) (fail :FUNDS "insufficent token balance")) - ;; Need this check in case of self-transfers. - (when (= *caller* addr) - (return amount)) - (-set-balance *caller* (- bal amount)) - (-set-balance addr (+ tbal amount)))) + ;; Need this in case of self-transfers. + (when (= *caller* addr) + (log "TR" *caller* addr amount bal bal data) + (return amount)) + + (let [nsb (- bal amount) + nrb (+ tbal amount)] + (log "TR" *caller* addr amount nsb nrb data) + (-set-balance *caller* (- bal amount)) + (-set-balance addr (+ tbal amount))) + )) ;;;;; Wrap and unwrap @@ -139,6 +145,7 @@ noff (- off quantity) nos (cond (<= noff 0) (dissoc os receiver) (assoc os receiver noff)) nrec [nbal nos]] + ;; Update offer and balance of sender (set-holding sender nrec) ;; Add accepted quantity adeed to receiver diff --git a/convex-core/src/main/cvx/convex/core/core.cvx b/convex-core/src/main/cvx/convex/core/core.cvx index 14e28faef..bf6643b6b 100644 --- a/convex-core/src/main/cvx/convex/core/core.cvx +++ b/convex-core/src/main/cvx/convex/core/core.cvx @@ -729,4 +729,14 @@ supply 1000000000000000000] (cond (>= i 8) supply - (recur (inc i) (- supply (balance (address i)))))))) \ No newline at end of file + (recur (inc i) (- supply (balance (address i)))))))) + + (defn scope + ^{:doc {:description "Gets the scope from a callable value, or nil if undefined" + :examples [{:code "(scope [#29 :foo])"}] + :signature [{:params [a]}]}} + [a] + (cond + (address? a) nil + (not (callable? a)) nil + (nth a 1))) \ No newline at end of file diff --git a/convex-core/src/main/java/convex/core/Constants.java b/convex-core/src/main/java/convex/core/Constants.java index 8aba13405..f31781c19 100644 --- a/convex-core/src/main/java/convex/core/Constants.java +++ b/convex-core/src/main/java/convex/core/Constants.java @@ -97,7 +97,7 @@ public class Constants { public static final AVector INITIAL_GLOBALS = Vectors.of( Constants.INITIAL_TIMESTAMP, Constants.INITIAL_FEES, Constants.INITIAL_JUICE_PRICE, Constants.INITIAL_MEMORY_POOL, - Constants.INITIAL_MEMORY_POOL * Constants.INITIAL_MEMORY_PRICE); + Constants.INITIAL_MEMORY_POOL * Constants.INITIAL_MEMORY_PRICE, -1L); /** * Maximum length of a symbolic name in bytes (keywords and symbols) diff --git a/convex-core/src/main/java/convex/core/cvm/Address.java b/convex-core/src/main/java/convex/core/cvm/Address.java index e2ba80fee..6afca2c8f 100644 --- a/convex-core/src/main/java/convex/core/cvm/Address.java +++ b/convex-core/src/main/java/convex/core/cvm/Address.java @@ -106,6 +106,7 @@ public boolean equals(ACell o) { } public final boolean equals(Address o) { + if (o==null) return false; return value==o.value; } diff --git a/convex-core/src/main/java/convex/core/cvm/Context.java b/convex-core/src/main/java/convex/core/cvm/Context.java index a9fcfb736..2d331b737 100644 --- a/convex-core/src/main/java/convex/core/cvm/Context.java +++ b/convex-core/src/main/java/convex/core/cvm/Context.java @@ -40,7 +40,6 @@ import convex.core.lang.Core; import convex.core.lang.RT; import convex.core.lang.impl.CoreFn; -import convex.core.lang.impl.TransactionContext; import convex.core.util.Economics; import convex.core.util.ErrorMessages; import convex.core.util.Utils; @@ -104,7 +103,7 @@ public class Context { private ChainState chainState; /** - * Local log is an ordered [vector of [address caller scope location [values ...] ] entries + * Local log is an ordered [vector of [address scope location [values ...] ] entries * See CAD33 for details */ private AVector> log; @@ -173,10 +172,10 @@ private ChainState(State state, TransactionContext transactionContext,Address ca this.scope=scope; } - public static ChainState create(State state, TransactionContext origin, Address caller, Address address, long offer, ACell scope) { + public static ChainState create(State state, TransactionContext tContext, Address caller, Address address, long offer, ACell scope) { AccountStatus as=state.getAccount(address); if (as==null) return null; - return new ChainState(state,origin,caller,address,as,offer,scope); + return new ChainState(state,tContext,caller,address,as,offer,scope); } public ChainState withStateOffer(State newState,long newOffer) { @@ -238,7 +237,7 @@ public AccountStatus getAccount() { } public Address getOrigin() { - return txContext.origin; + return txContext.getOrigin(); } public AccountStatus getOriginAccount() { @@ -251,6 +250,15 @@ public AccountKey getPeer() { return txContext.getPeer(); } + public ChainState withTransactionContext(TransactionContext tctx) { + if (txContext==tctx) return this; + return create(state,tctx,caller,address,offer,scope); + } + + public TransactionContext getTransactionContext() { + return txContext; + } + } protected Context(ChainState chainState, long juice, long juiceLimit, AVector localBindings2,ACell result, int depth, AExceptional exception, AVector> log, CompilerState comp) { @@ -272,8 +280,7 @@ private static Context create(ChainState cs, long juice,long j return ctx; } - private static Context create(State state, long juice,long juiceLimit,AVector localBindings, T result, int depth, Address origin,Address caller, Address address, long offer, AVector> log, CompilerState comp) { - TransactionContext tctx=TransactionContext.createQuery(state, origin); + private static Context create(State state, TransactionContext tctx,long juice,long juiceLimit,AVector localBindings, T result, int depth, Address origin,Address caller, Address address, long offer, AVector> log, CompilerState comp) { ChainState chainState=ChainState.create(state,tctx,caller,address,offer,NULL_SCOPE); if (chainState==null) throw new Error("Attempting to create context with invalid Address"); return create(chainState,juice,juiceLimit,localBindings,result,depth,log,comp); @@ -302,8 +309,7 @@ public static Context create(State state) { * @return Fake context */ public static Context create(State state, Address origin) { - if (origin==null) throw new IllegalArgumentException("Null address!"); - return create(state,0,Constants.MAX_TRANSACTION_JUICE,EMPTY_BINDINGS,NO_RESULT,ZERO_DEPTH,origin,null,origin, ZERO_OFFER, DEFAULT_LOG,null); + return create(state,origin,Constants.MAX_TRANSACTION_JUICE); } /** @@ -318,13 +324,15 @@ public static Context create(State state, Address origin) { * @return Initial execution context with reserved juice. */ public static Context create(State state, Address origin,long juiceLimit) { + if (origin==null) throw new IllegalArgumentException("Null address!"); + TransactionContext tctx=TransactionContext.create(state); + tctx.origin=origin; AccountStatus as=state.getAccount(origin); if (as==null) { // no account return Context.create(state).withError(ErrorCodes.NOBODY); } - - return create(state,0,juiceLimit,EMPTY_BINDINGS,NO_RESULT,ZERO_DEPTH,origin,null,origin,INITIAL_JUICE,DEFAULT_LOG,null); + return create(state,tctx,0,juiceLimit,EMPTY_BINDINGS,NO_RESULT,ZERO_DEPTH,origin,null,origin,INITIAL_JUICE,DEFAULT_LOG,null); } @@ -1344,7 +1352,7 @@ public Context evalAs(Address target, ACell form) { if (!canControl) return ctx.withError(ErrorCodes.TRUST,"Cannot control address: "+target); // SECURITY: eval with a context switch - final Context exContext=Context.create(ctx.getState(), ctx.juice,juiceLimit, EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),caller, target,ZERO_OFFER,ctx.log,NO_COMPILER_STATE); + final Context exContext=Context.create(ctx.getState(),getTransactionContext(), ctx.juice,juiceLimit, EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),caller, target,ZERO_OFFER,ctx.log,NO_COMPILER_STATE); final Context rContext=exContext.eval(form); // SECURITY: must handle results as if returning from an actor call @@ -1715,13 +1723,17 @@ public Context actorCall(ACell target, long offer, ACell functionName, ACell... * @return */ private Context forkActorCall(State state, Address target, long offer, ACell scope) { - Context fctx=Context.create(state, juice, juiceLimit,EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),getAddress(), target,offer, log,NO_COMPILER_STATE); + Context fctx=Context.create(state, getTransactionContext(),juice, juiceLimit,EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),getAddress(), target,offer, log,NO_COMPILER_STATE); if (scope!=null) { fctx.chainState=fctx.chainState.withScope(scope); } return fctx; } + private TransactionContext getTransactionContext() { + return chainState.getTransactionContext(); + } + /** * Handle results at the end of an execution boundary (actor call, transaction etc.) * @param returnContext Context containing return from child transaction / call @@ -1821,7 +1833,7 @@ public Context deploy(ACell... code) { State stateSetup=initialState.addActor(); // Deployment execution context with forked context and incremented depth - Context ctx=Context.create(stateSetup, juice, juiceLimit,EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),getAddress(), address,ZERO_OFFER,log,NO_COMPILER_STATE); + Context ctx=Context.create(stateSetup, getTransactionContext(), juice, juiceLimit,EMPTY_BINDINGS, NO_RESULT, depth+1, getOrigin(),getAddress(), address,ZERO_OFFER,log,NO_COMPILER_STATE); for (int i=0; i values) { if (log==null) { log=Vectors.empty(); } - AVector entry = Vectors.of(addr,scope,null,values); + AVector location=getLocation(); + AVector entry = Vectors.of(addr,scope,location,values); log=log.conj(entry); this.log=log; @@ -2361,10 +2374,29 @@ public AFn lookupExpander(ACell form) { * @return Peer key, or null if outside a peer created block */ public AccountKey getPeer() { - // TODO Auto-generated method stub return chainState.getPeer(); } + /** + * Gets the most recent log entry, or null if not available. + * @return + */ + public AVector lastLog() { + AVector> log=getLog(); + long n=log.count(); + if (n==0) return null; + return log.get(n-1); + } + + public Context withTransactionContext(TransactionContext tctx) { + return withChainState(chainState.withTransactionContext(tctx)); + } + + public AVector getLocation() { + + return chainState.txContext.getLocation(); + } + } diff --git a/convex-core/src/main/java/convex/core/cvm/Peer.java b/convex-core/src/main/java/convex/core/cvm/Peer.java index 493ba09f8..5bbc25120 100644 --- a/convex-core/src/main/java/convex/core/cvm/Peer.java +++ b/convex-core/src/main/java/convex/core/cvm/Peer.java @@ -333,7 +333,8 @@ public ResultContext executeQuery(ACell form, Address address) { * @return The Context containing the transaction results. */ public ResultContext executeDetached(ATransaction transaction) { - ResultContext ctx=getConsensusState().applyTransaction(transaction); + State s=getConsensusState(); + ResultContext ctx=getConsensusState().applyTransaction(transaction,TransactionContext.create(s)); return ctx; } diff --git a/convex-core/src/main/java/convex/core/cvm/State.java b/convex-core/src/main/java/convex/core/cvm/State.java index 226e33dfa..62b0cf150 100644 --- a/convex-core/src/main/java/convex/core/cvm/State.java +++ b/convex-core/src/main/java/convex/core/cvm/State.java @@ -38,7 +38,6 @@ import convex.core.exceptions.InvalidDataException; import convex.core.init.Init; import convex.core.lang.RT; -import convex.core.lang.impl.TransactionContext; import convex.core.util.Counters; import convex.core.util.Economics; import convex.core.util.Utils; @@ -75,6 +74,7 @@ public class State extends ARecordGeneric { Symbols.JUICE_PRICE, Symbols.MEMORY, Symbols.MEMORY_VALUE, + Symbols.BLOCK, Symbols.PROTOCOL); // Indexes for globals in :globals Vector @@ -83,7 +83,8 @@ public class State extends ARecordGeneric { public static final int GLOBAL_JUICE_PRICE=2; public static final int GLOBAL_MEMORY_MEM=3; public static final int GLOBAL_MEMORY_CVX=4; - public static final int GLOBAL_PROTOCOL=5; // TODO: move to actor? + public static final int GLOBAL_BLOCK=5; + public static final int GLOBAL_PROTOCOL=6; // TODO: move to actor? /** * An empty State @@ -273,6 +274,9 @@ public BlockResult checkBlock(SignedData signedBlock) { */ private State prepareBlock(Block b) { State state = this; + AVector globals=state.getGlobals(); + long blockNum=getBlockNumber(); + state = state.withGlobals(globals.assoc(GLOBAL_BLOCK,CVMLong.create(blockNum+1))); state = state.applyTimeUpdate(b.getTimeStamp()); state = state.applyScheduledTransactions(); return state; @@ -394,9 +398,10 @@ private BlockResult applyTransactions(Block block, TransactionContext tctx) thro // Update transaction context tctx.tx=signed; + tctx.txNumber=i; // execute the transaction using the *latest* state (not necessarily "this") - ResultContext rc = state.applyTransaction(signed); + ResultContext rc = state.applyTransaction(signed,tctx); // record results from result context results[i] = Result.fromContext(CVMLong.create(i),rc); @@ -456,16 +461,19 @@ static State distributeFees(State state, AccountKey peer, long fees) { * Applies a signed transaction to the State. * * SECURITY: Checks digital signature and correctness of account key + * @param tctx * * @return ResultContext containing the result of the transaction * @throws InvalidBlockException */ - public ResultContext applyTransaction(SignedData signedTransaction) throws InvalidBlockException { + public ResultContext applyTransaction(SignedData t2, TransactionContext tctx) throws InvalidBlockException { // Extract transaction - ATransaction t=RT.ensureTransaction(signedTransaction.getValue()); - if (t==null) throw new InvalidBlockException("Not a signed transaction: "+signedTransaction.getHash()); + ATransaction t=RT.ensureTransaction(t2.getValue()); + if (t==null) throw new InvalidBlockException("Not a signed transaction: "+t2.getHash()); Address addr=t.getOrigin(); + tctx.origin=addr; + AccountStatus as = getAccount(addr); if (as==null) { ResultContext rc=ResultContext.error(this,ErrorCodes.NOBODY,"Transaction for non-existent Account: "+addr); @@ -486,14 +494,14 @@ public ResultContext applyTransaction(SignedData signedT } // Perform Signature check - boolean sigValid=signedTransaction.checkSignature(key); + boolean sigValid=t2.checkSignature(key); if (!sigValid) { ResultContext rc= ResultContext.error(this,ErrorCodes.SIGNATURE, Strings.BAD_SIGNATURE); return rc.withSource(SourceCodes.CVM); } } - ResultContext ctx=applyTransaction(t); + ResultContext ctx=applyTransaction(t,tctx); return ctx; } @@ -512,11 +520,12 @@ public ResultContext applyTransaction(SignedData signedT * @param t Transaction to apply * @return Context containing the updated chain State (may be exceptional) */ - public ResultContext applyTransaction(ATransaction t) { + public ResultContext applyTransaction(ATransaction t, TransactionContext tctx) { ResultContext rc=createResultContext(t); // Create prepared context - Context ctx = prepareTransaction(rc); + Context ctx = prepareTransaction(rc,tctx); + if (!ctx.isExceptional()) { State preparedState=ctx.getState(); @@ -540,15 +549,26 @@ public ResultContext applyTransaction(ATransaction t) { return rc.withContext(ctx); } + /** + * Apply a transaction in a detached transaction context, mainly for test / query + * @param t Transaction + * @return + */ + public ResultContext applyTransaction(ATransaction t) { + return applyTransaction(t,TransactionContext.create(this)); + } + /** * Prepares a CVM execution context and ResultContext for a transaction * @param rc ResultContext to populate + * @param tctx * @return */ - private Context prepareTransaction(ResultContext rc) { + private Context prepareTransaction(ResultContext rc, TransactionContext tctx) { ATransaction t=rc.tx; long juicePrice=rc.juicePrice; Address origin = t.getOrigin(); + tctx.origin=origin; // Pre-transaction state updates (persisted even if transaction fails) AccountStatus account = getAccount(origin); @@ -568,6 +588,7 @@ private Context prepareTransaction(ResultContext rc) { // Create context ready to execute, with at least some available juice Context ctx = Context.create(this, origin, juiceLimit); ctx=ctx.withJuice(initialJuice); + ctx=ctx.withTransactionContext(tctx); return ctx; } @@ -975,6 +996,12 @@ protected State withValues(AVector newValues) { return create(newValues); } + public long getBlockNumber() { + + return RT.ensureLong(getGlobals().get(GLOBAL_BLOCK)).longValue(); + } + + diff --git a/convex-core/src/main/java/convex/core/cvm/Symbols.java b/convex-core/src/main/java/convex/core/cvm/Symbols.java index f0e087a01..9e1470eb6 100644 --- a/convex-core/src/main/java/convex/core/cvm/Symbols.java +++ b/convex-core/src/main/java/convex/core/cvm/Symbols.java @@ -196,6 +196,8 @@ public class Symbols { public static final Symbol STAR_MEMORY_PRICE = intern("*memory-price*"); public static final Symbol STAR_SIGNER = intern("*signer*"); public static final Symbol STAR_PEER = intern("*peer*"); + public static final Symbol STAR_LOCATION = intern("*location*"); + public static final Symbol STAR_LANG = intern("*lang*"); @@ -347,6 +349,7 @@ public class Symbols { public static final Symbol MEMORY_VALUE = intern("memory-value"); public static final Symbol PROTOCOL = intern("protocol"); public static final Symbol CREATE = intern("create"); + public static final Symbol BLOCK = intern("block"); diff --git a/convex-core/src/main/java/convex/core/lang/impl/TransactionContext.java b/convex-core/src/main/java/convex/core/cvm/TransactionContext.java similarity index 62% rename from convex-core/src/main/java/convex/core/lang/impl/TransactionContext.java rename to convex-core/src/main/java/convex/core/cvm/TransactionContext.java index 5ae07e0d2..be0b92d3d 100644 --- a/convex-core/src/main/java/convex/core/lang/impl/TransactionContext.java +++ b/convex-core/src/main/java/convex/core/cvm/TransactionContext.java @@ -1,23 +1,25 @@ -package convex.core.lang.impl; +package convex.core.cvm; import convex.core.cpos.Block; -import convex.core.cvm.Address; -import convex.core.cvm.State; import convex.core.cvm.transactions.ATransaction; +import convex.core.data.AVector; import convex.core.data.AccountKey; import convex.core.data.SignedData; +import convex.core.data.Vectors; +import convex.core.data.prim.CVMLong; public final class TransactionContext { public SignedData tx; public SignedData block; public Address origin; public State initialState; + public long blockNumber; + public long txNumber=0; public static TransactionContext createQuery(State initialState, Address origin) { - TransactionContext ctx=new TransactionContext(); + TransactionContext ctx=create(initialState); ctx.origin=origin; - ctx.initialState=initialState; return ctx; } @@ -29,6 +31,15 @@ public AccountKey getPeer() { public static TransactionContext create(State state) { TransactionContext ctx=new TransactionContext(); ctx.initialState=state; + ctx.blockNumber=state.getBlockNumber(); return ctx; } + + public Address getOrigin() { + return origin; + } + + public AVector getLocation() { + return Vectors.createLongs(blockNumber,txNumber); + } } diff --git a/convex-core/src/main/java/convex/core/cvm/ops/Special.java b/convex-core/src/main/java/convex/core/cvm/ops/Special.java index 9a18602c3..29cec6b14 100644 --- a/convex-core/src/main/java/convex/core/cvm/ops/Special.java +++ b/convex-core/src/main/java/convex/core/cvm/ops/Special.java @@ -30,7 +30,7 @@ public class Special extends AOp { private final byte specialCode; - public static final int NUM_SPECIALS=24; + public static final int NUM_SPECIALS=25; private static final int BASE=0; private static final int LIMIT=BASE+NUM_SPECIALS; public static final Symbol[] SYMBOLS=new Symbol[NUM_SPECIALS]; @@ -61,6 +61,7 @@ public class Special extends AOp { private static final byte S_MEMORY_PRICE=BASE+21; private static final byte S_SIGNER=BASE+22; private static final byte S_PEER=BASE+23; + private static final byte S_LOCATION=BASE+24; static { reg(S_JUICE,Symbols.STAR_JUICE); @@ -87,6 +88,7 @@ public class Special extends AOp { reg(S_MEMORY_PRICE,Symbols.STAR_MEMORY_PRICE); reg(S_SIGNER,Symbols.STAR_SIGNER); reg(S_PEER,Symbols.STAR_PEER); + reg(S_LOCATION,Symbols.STAR_LOCATION); } private static byte reg(byte opCode, Symbol sym) { @@ -141,6 +143,7 @@ public Context execute(Context ctx) { case S_MEMORY_PRICE: ctx=ctx.withResult(CVMDouble.create(ctx.getState().getMemoryPrice())); break ; case S_SIGNER: ctx=ctx.withResult(null); break; // TODO case S_PEER: ctx=ctx.withResult(ctx.getPeer()); break ; // TODO + case S_LOCATION: ctx=ctx.withResult(ctx.getLocation()); break ; // TODO default: throw new Error("Bad Opcode"+specialCode); diff --git a/convex-core/src/main/java/convex/core/lang/RT.java b/convex-core/src/main/java/convex/core/lang/RT.java index 97e6f36f4..b2e57e0db 100644 --- a/convex-core/src/main/java/convex/core/lang/RT.java +++ b/convex-core/src/main/java/convex/core/lang/RT.java @@ -1192,6 +1192,11 @@ public static AFn ensureFunction(ACell a) { public static Address castAddress(ACell a) { if (a instanceof Address) return (Address) a; + if (a instanceof AVector) { + AVector v=RT.ensureVector(a); + if (v.count()==0) return null; + return ensureAddress(v.get(0)); + } if (a instanceof ABlob) return Address.create((ABlob) a); CVMLong value = RT.ensureLong(a); diff --git a/convex-core/src/test/java/convex/core/StateTransitionsTest.java b/convex-core/src/test/java/convex/core/StateTransitionsTest.java index 3af09c2ca..a03dfaf2e 100644 --- a/convex-core/src/test/java/convex/core/StateTransitionsTest.java +++ b/convex-core/src/test/java/convex/core/StateTransitionsTest.java @@ -18,6 +18,8 @@ import convex.core.cvm.Juice; import convex.core.cvm.PeerStatus; import convex.core.cvm.State; +import convex.core.cvm.TransactionContext; +import convex.core.cvm.impl.InvalidBlockException; import convex.core.cvm.transactions.ATransaction; import convex.core.cvm.transactions.Invoke; import convex.core.cvm.transactions.Transfer; @@ -238,6 +240,27 @@ public void testAccountTransfers() throws BadSignatureException { } } + + @Test + public void testBlockNumbering() throws BadSignatureException { + State s = TestState.STATE; + + assertEquals(-1,s.getBlockNumber()); + + ATransaction t1 = Invoke.create(InitTest.HERO,1,Reader.read("(log :foo)")); + ATransaction t2 = Invoke.create(InitTest.HERO,2,Reader.read("(log :bar)")); + AKeyPair kp = InitTest.HERO_KEYPAIR; + Block b1 = Block.of(s.getTimestamp().longValue(), kp.signData(t1), kp.signData(t2)); + SignedData sb=KEYPAIR_PEER.signData(b1); + BlockResult br=s.applyBlock(sb); + assertEquals(0,br.getState().getBlockNumber()); + + assertEquals(Vectors.of(0,0),br.getResult(0).getLog().get(0).get(2)); + assertEquals(Vectors.of(0,1),br.getResult(1).getLog().get(0).get(2)); + + s = br.getState(); + + } @Test public void testDeploys() throws BadSignatureException { @@ -318,10 +341,10 @@ public void testBadBlockInsufficientStake() throws BadSignatureException { } } - @Test public void testDefTransaction() { + @Test public void testDefTransaction() throws InvalidBlockException { State s = TestState.STATE; ATransaction t1 = Invoke.create(InitTest.HERO,1,Reader.read("(def a 1)")); - ResultContext rc=s.applyTransaction(t1); + ResultContext rc=s.applyTransaction(t1,TransactionContext.create(s)); assertFalse(rc.isError()); State s2=rc.getState(); AccountStatus as=s2.getAccount(InitTest.HERO); diff --git a/convex-core/src/test/java/convex/core/TransactionTest.java b/convex-core/src/test/java/convex/core/TransactionTest.java index 8908c5483..7182514c9 100644 --- a/convex-core/src/test/java/convex/core/TransactionTest.java +++ b/convex-core/src/test/java/convex/core/TransactionTest.java @@ -22,6 +22,7 @@ import convex.core.cvm.Keywords; import convex.core.cvm.State; import convex.core.cvm.Symbols; +import convex.core.cvm.TransactionContext; import convex.core.cvm.impl.InvalidBlockException; import convex.core.cvm.transactions.ATransaction; import convex.core.cvm.transactions.Call; @@ -238,8 +239,8 @@ public void testInvoke() { @Test public void testBadSequence() throws BadSignatureException, InvalidBlockException { Invoke t1=Invoke.create(HERO, 2, "(+ 2 5)"); - SignedData st = Samples.KEY_PAIR.signData(t1); - ResultContext rc=state().applyTransaction(st); + SignedData st = Samples.KEY_PAIR.signData(t1); + ResultContext rc=state().applyTransaction(st,TransactionContext.create(state())); Context ctx=rc.context; assertEquals(ErrorCodes.SEQUENCE,ctx.getError().getCode()); @@ -262,27 +263,28 @@ public void testBadSequence() throws BadSignatureException, InvalidBlockExceptio State s=state(); AccountStatus as=s.getAccount(HERO); long SEQ=as.getSequence()+1; + TransactionContext tctx=TransactionContext.create(s); { // wrong sequence - ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(HERO, SEQ+1,Keywords.FOO))); + ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(HERO, SEQ+1,Keywords.FOO)),tctx); assertEquals(ErrorCodes.SEQUENCE,rc.getErrorCode()); checkNoTransactionEffects(s,rc); } { // non-existent account - ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(Address.create(777777), SEQ,Keywords.FOO))); + ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(Address.create(777777), SEQ,Keywords.FOO)),tctx); assertEquals(ErrorCodes.NOBODY,rc.getErrorCode()); checkNoTransactionEffects(s,rc); } { // wrong key - ResultContext rc=s.applyTransaction(VILLAIN_KP.signData(Invoke.create(HERO, SEQ,Keywords.FOO))); + ResultContext rc=s.applyTransaction(VILLAIN_KP.signData(Invoke.create(HERO, SEQ,Keywords.FOO)),tctx); assertEquals(ErrorCodes.SIGNATURE,rc.getErrorCode()); checkNoTransactionEffects(s,rc); } { // account without public key - ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(Address.ZERO, SEQ,Keywords.FOO))); + ResultContext rc=s.applyTransaction(HERO_KP.signData(Invoke.create(Address.ZERO, SEQ,Keywords.FOO)),tctx); assertEquals(ErrorCodes.STATE,rc.getErrorCode()); checkNoTransactionEffects(s,rc); } @@ -291,7 +293,7 @@ public void testBadSequence() throws BadSignatureException, InvalidBlockExceptio // signed something other than a transaction @SuppressWarnings("rawtypes") SignedData st = (SignedData)HERO_KP.signData(Keywords.FOO); - assertThrows(InvalidBlockException.class, ()->s.applyTransaction(st)); + assertThrows(InvalidBlockException.class, ()->s.applyTransaction(st,tctx)); } } @@ -317,7 +319,7 @@ public void testJuiceFail() throws InvalidBlockException { AccountStatus as=s.getAccount(HERO); long SEQ=as.getSequence()+1; SignedData st=HERO_KP.signData(Invoke.create(HERO, SEQ,"(loop [] (def a 2) (recur))")); - ResultContext rc=s.applyTransaction(st); + ResultContext rc=s.applyTransaction(st,TransactionContext.create(s)); assertEquals(ErrorCodes.JUICE,rc.getErrorCode()); assertEquals(SourceCodes.CVM,rc.getSource()); assertSame(st.getValue(),rc.tx); diff --git a/convex-core/src/test/java/convex/core/lang/CoreTest.java b/convex-core/src/test/java/convex/core/lang/CoreTest.java index cd2d41964..2600568df 100644 --- a/convex-core/src/test/java/convex/core/lang/CoreTest.java +++ b/convex-core/src/test/java/convex/core/lang/CoreTest.java @@ -769,6 +769,29 @@ public void testLog() { assertEquals(2,log.count()); // should be two entries now assertEquals(v0,log.get(0).get(Log.P_VALUES)); assertEquals(v1,log.get(1).get(Log.P_VALUES)); + + { + // logs work inside deploys + Context ctx=step("(deploy '(log :foo))"); + Address addr=ctx.getResult(); + assertEquals(eval("["+addr+" nil *location* [:foo]]"),ctx.lastLog()); + } + + { + // logs get rolled back + Context ctx=step(context(),"(log :foo)"); + ctx=step(ctx,"(query (log bar))"); // should get rolled back + assertEquals(eval("[*address* nil *location* [:foo]]"),ctx.lastLog()); + } + + { + // logs work inside calls + Context ctx=context(); + ctx=exec(ctx,"(def addr (deploy '(defn foo ^:callable [] (log :foo))))"); + Address addr=ctx.getResult(); + ctx=exec(ctx,"(call addr (foo))"); + assertEquals(eval("["+addr+" nil *location* [:foo]]"),ctx.lastLog()); + } } @@ -1246,6 +1269,9 @@ public void testNth() { // cast errors for bad indexes assertCastError(step("(nth [] :foo)")); assertCastError(step("(nth [] nil)")); + + assertNull(eval("(nth [1 nil] 1)")); + // cast errors for non-countable objects assertCastError(step("(nth 12 13)")); @@ -4934,7 +4960,7 @@ public void testScheduleStar() throws BadSignatureException { @Test public void testScheduleExecution() throws BadSignatureException { long expectedTS = INITIAL.getTimestamp().longValue() + 1000; - Context ctx = step("(schedule (+ *timestamp* 1000) (def a 2))"); + Context ctx = exec(context(),"(schedule (+ *timestamp* 1000) (def a 2))"); assertCVMEquals(expectedTS, ctx.getResult()); State s = ctx.getState(); Index> sched = s.getSchedule(); @@ -5132,6 +5158,17 @@ public void testCallableQ() { assertCastError(step(ctx, "(callable? caddr nil)")); assertCastError(step(ctx, "(callable? caddr 1)")); } + + @Test + public void testScope() { + assertEquals(Keywords.FOO,eval("(scope [#17 :foo])")); + assertNull(eval("(scope #11)")); + assertNull(eval("(scope [#11 nil])")); + assertNull(eval("(scope true)")); + + assertArityError(step("(scope)")); + assertArityError(step("(scope)")); + } @Test public void testDec() { diff --git a/convex-core/src/test/java/convex/lib/AssetTester.java b/convex-core/src/test/java/convex/lib/AssetTester.java index cd0cdf342..a730dbcda 100644 --- a/convex-core/src/test/java/convex/lib/AssetTester.java +++ b/convex-core/src/test/java/convex/lib/AssetTester.java @@ -147,17 +147,21 @@ public static void doFungibleTests(Context ctx, ACell token, Address user) { } // transfer all to self, should not affect balance - ctx = step(ctx, "(asset/transfer *address* [token " + BAL + "])"); - assertEquals(BAL, RT.jvm(ctx.getResult())); - assertEquals(BAL, evalL(ctx, "(asset/balance token *address*)")); + { + String cmd="(asset/transfer *address* [token " + BAL + "])"; + ctx = exec(ctx, cmd); + assertEquals(eval(ctx,"[(address token) (scope token) *location* [\"TR\" *address* *address* "+BAL+" "+BAL+" "+BAL+" nil]]"),ctx.lastLog()); + assertEquals(BAL, RT.jvm(ctx.getResult())); + assertEquals(BAL, evalL(ctx, "(asset/balance token *address*)")); + } // transfer nothing to self, should not affect balance - ctx = step(ctx, "(asset/transfer *address* [token nil])"); + ctx = exec(ctx, "(asset/transfer *address* [token nil])"); assertEquals(0L, (long) RT.jvm(ctx.getResult())); assertEquals(BAL, evalL(ctx, "(asset/balance token *address*)")); // set a zero offer - ctx = step(ctx, "(asset/offer *address* [token 0])"); + ctx = exec(ctx, "(asset/offer *address* [token 0])"); assertCVMEquals(0, ctx.getResult()); assertCVMEquals(0, eval(ctx, "(asset/get-offer token *address* *address*)")); @@ -167,7 +171,7 @@ public static void doFungibleTests(Context ctx, ACell token, Address user) { // set a non-zero offer - ctx = step(ctx, "(asset/offer *address* token 666)"); + ctx = exec(ctx, "(asset/offer *address* token 666)"); assertCVMEquals(666, eval(ctx, "(asset/get-offer token *address* *address*)")); // nil offer sets to zero diff --git a/convex-core/src/test/java/convex/lib/FungibleTest.java b/convex-core/src/test/java/convex/lib/FungibleTest.java index 689ac39ec..bf1d27c46 100644 --- a/convex-core/src/test/java/convex/lib/FungibleTest.java +++ b/convex-core/src/test/java/convex/lib/FungibleTest.java @@ -1,6 +1,11 @@ package convex.lib; -import static convex.test.Assertions.*; +import static convex.test.Assertions.assertArgumentError; +import static convex.test.Assertions.assertCVMEquals; +import static convex.test.Assertions.assertFundsError; +import static convex.test.Assertions.assertNotError; +import static convex.test.Assertions.assertStateError; +import static convex.test.Assertions.assertTrustError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,6 +20,7 @@ import convex.core.data.Symbol; import convex.core.init.InitTest; import convex.core.lang.ACVMTest; +import convex.core.lang.RT; import convex.core.lang.TestState; public class FungibleTest extends ACVMTest { @@ -108,8 +114,6 @@ private static State buildState() { assertTrue(ctx.getAccountStatus(token)!=null); ctx=exec(ctx,"(def token (address "+token+"))"); - // GEnric tests - AssetTester.doFungibleTests(ctx,token,ctx.getAddress()); // check our balance is positive as initial holder long bal=evalL(ctx,"(fungible/balance token *address*)"); @@ -117,9 +121,12 @@ private static State buildState() { // transfer to the Villain scenario { - Context tctx=step(ctx,"(fungible/transfer token "+VILLAIN+" 100)"); + String code="(fungible/transfer token "+VILLAIN+" 100)"; + Context tctx=step(ctx,code); assertEquals(bal-100,evalL(tctx,"(fungible/balance token *address*)")); assertEquals(100,evalL(tctx,"(fungible/balance token "+VILLAIN+")")); + + assertEquals(eval(ctx,"[(address token) (scope token) *location* [\"TR\" *address* "+VILLAIN+" 100 "+(bal-100)+" 100 nil]]"),tctx.lastLog()); } // acceptable transfers @@ -129,6 +136,26 @@ private static State buildState() { // bad transfers assertArgumentError(step(ctx,"(fungible/transfer token *address* -1)")); assertFundsError(step(ctx,"(fungible/transfer token *address* "+(bal+1)+")")); + + // GEnric tests + AssetTester.doFungibleTests(ctx,token,ctx.getAddress()); + + } + + @Test public void testTransferLog() { + Context ctx = context(); + ctx=exec(ctx,"(def token (deploy (fungible/build-token {:supply 1000000})))"); + Address token = (Address) ctx.getResult(); + assertTrue(ctx.getAccountStatus(token)!=null); + ctx=exec(ctx,"(def token (address "+token+"))"); + + long BAL=evalL(ctx,"(asset/balance token)"); + + String cmd="(asset/transfer *address* [token " + BAL + "])"; + ctx = exec(ctx, cmd); + assertEquals(eval(ctx,"[(address token) (scope token) *location* [\"TR\" *address* *address* "+BAL+" "+BAL+" "+BAL+" nil]]"),ctx.lastLog()); + assertEquals(BAL, RT.ensureLong(ctx.getResult()).longValue()); + assertEquals(BAL, evalL(ctx, "(asset/balance token *address*)")); } @Test public void testMint() { @@ -152,6 +179,7 @@ private static State buildState() { // Mint up to max and back down to zero { Context c=exec(ctx,"(fungible/mint token 900)"); + assertEquals(eval(ctx,"[\"MINT\" *address* 900 1000]"),c.lastLog().getLast()); assertEquals(1000L,evalL(c,"(fungible/balance token *address*)")); c=exec(c,"(fungible/mint token -900)");