diff --git a/x/contracts/runtime/call_context_test.go b/x/contracts/runtime/call_context_test.go index a0ed808552..7f4a64bff7 100644 --- a/x/contracts/runtime/call_context_test.go +++ b/x/contracts/runtime/call_context_test.go @@ -65,6 +65,57 @@ func TestCallContext(t *testing.T) { require.Nil(result) } +func TestValueContext(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + contract0ID := ids.GenerateTestID() + contract0Address := codec.CreateAddress(0, contract0ID) + stringedID0 := string(contract0ID[:]) + + defaultActorAddress := codec.Address{} + + value := uint64(123456) + + testStateManager := &TestStateManager{ + ContractsMap: map[string][]byte{}, + AccountMap: map[codec.Address]string{contract0Address: stringedID0}, + Balances: map[codec.Address]uint64{defaultActorAddress: value}, + } + + err := testStateManager.CompileAndSetContract(ContractID(stringedID0), "call_contract") + require.NoError(err) + + r := NewRuntime( + NewConfig(), + logging.NoLog{}, + ).WithDefaults( + CallInfo{ + Contract: contract0Address, + State: testStateManager, + Fuel: 1000000, + }) + + result, err := r.WithValue(value).CallContract( + ctx, + &CallInfo{ + Actor: r.defaultCallInfo.Actor, + FunctionName: "value_check", + }) + require.NoError(err) + require.Equal(value, into[uint64](result)) + + result, err = r.CallContract( + ctx, + &CallInfo{ + FunctionName: "value_check", + }) + require.NoError(err) + require.Equal(uint64(0), into[uint64](result)) +} + func TestCallContextPreventOverwrite(t *testing.T) { require := require.New(t) ctx := context.Background() diff --git a/x/contracts/runtime/contract.go b/x/contracts/runtime/contract.go index 8b9341630b..dbdca90efc 100644 --- a/x/contracts/runtime/contract.go +++ b/x/contracts/runtime/contract.go @@ -27,6 +27,7 @@ type Context struct { Actor codec.Address Height uint64 Timestamp uint64 + Value uint64 ActionID ids.ID } @@ -54,11 +55,12 @@ type CallInfo struct { // the timestamp of the chain at the time this call was made Timestamp uint64 + // the value that is passed to the call context + Value uint64 + // the action id that triggered this call ActionID ids.ID - Value uint64 - inst *ContractInstance } @@ -104,6 +106,7 @@ func (p *ContractInstance) call(ctx context.Context, callInfo *CallInfo) ([]byte Actor: callInfo.Actor, Height: callInfo.Height, Timestamp: callInfo.Timestamp, + Value: callInfo.Value, ActionID: callInfo.ActionID, } paramsBytes, err := Serialize(contractCtx) diff --git a/x/contracts/test/contracts/call_contract/src/lib.rs b/x/contracts/test/contracts/call_contract/src/lib.rs index e556f82d1c..13e888ceba 100644 --- a/x/contracts/test/contracts/call_contract/src/lib.rs +++ b/x/contracts/test/contracts/call_contract/src/lib.rs @@ -25,6 +25,11 @@ pub fn actor_check_external(ctx: &mut Context, target: Address, max_units: Gas) .expect("failure") } +#[public] +pub fn value_check(context: &mut Context) -> u64 { + context.value() +} + #[public] pub fn call_with_param(_: &mut Context, value: i64) -> i64 { value diff --git a/x/contracts/wasmlanche/src/context.rs b/x/contracts/wasmlanche/src/context.rs index 6860e53724..5905df8702 100644 --- a/x/contracts/wasmlanche/src/context.rs +++ b/x/contracts/wasmlanche/src/context.rs @@ -22,6 +22,7 @@ pub struct Context { actor: Address, height: u64, timestamp: u64, + value: u64, action_id: Id, state_cache: Cache, host_accessor: Accessor, @@ -47,6 +48,7 @@ mod debug { actor, height, timestamp, + value, action_id, state_cache: _, host_accessor: _, @@ -59,6 +61,7 @@ mod debug { actor, height, timestamp, + value, action_id ) } @@ -71,6 +74,7 @@ impl BorshDeserialize for Context { let actor = Address::deserialize_reader(reader)?; let height = u64::deserialize_reader(reader)?; let timestamp = u64::deserialize_reader(reader)?; + let value = u64::deserialize_reader(reader)?; let action_id = Id::deserialize_reader(reader)?; let ctx = Context { @@ -78,6 +82,7 @@ impl BorshDeserialize for Context { actor, height, timestamp, + value, action_id, state_cache: Cache::new(), host_accessor: Accessor::new(), @@ -125,6 +130,14 @@ impl Context { self.timestamp } + /// Returns the call-value + /// # Panics + /// Panics if the context was not injected + #[must_use] + pub fn value(&self) -> u64 { + self.value + } + /// Returns the action-id /// # Panics /// Panics if the context was not injected @@ -270,6 +283,7 @@ impl Context { actor: Address::default(), height: 0, timestamp: 0, + value: 0, action_id: Id::default(), state_cache: Cache::new(), host_accessor: Accessor::new(), diff --git a/x/contracts/wasmlanche/tests/wasmtime-integration.rs b/x/contracts/wasmlanche/tests/wasmtime-integration.rs index 2869e39ba5..8512ee6165 100644 --- a/x/contracts/wasmlanche/tests/wasmtime-integration.rs +++ b/x/contracts/wasmlanche/tests/wasmtime-integration.rs @@ -248,6 +248,7 @@ impl TestCrate { let mut actor = vec![0; Address::LEN]; let height: u64 = 0; let timestamp: u64 = 0; + let value: u64 = 0; let mut action_id = vec![1; ID_LEN]; // this is a hack to create a context since the constructor is private @@ -255,6 +256,7 @@ impl TestCrate { serialized_context.append(&mut actor); serialized_context.append(&mut height.to_le_bytes().to_vec()); serialized_context.append(&mut timestamp.to_le_bytes().to_vec()); + serialized_context.append(&mut value.to_le_bytes().to_vec()); serialized_context.append(&mut action_id); self.allocate(serialized_context)