# ActionDecoder

In order to reproduce user behaviour in existing pools, data is pulled from several sources and combined into a temporal sequence of actions, in a json file (see [Adding On-chain Transaction Data](../onchaintransactions))

ActionDecoder reads the actions from the json every simulation step and converts the actions into pool "opcodes", each represented by a class in `pool_method_entities.py` . These are passed to the pool state update function `s_update_pool.py`

First action (`pool_creation)`is skipped, since the results are already in the initial state variables

There are two main types of actions:

**a) Pool actions**\
**b) Price updates**

1. **Pool actions** call pool related policies, like this `swap`:

```
{
		"timestamp": "2021-01-13T18:46:38+00:00",
		"tx_hash": "0x8819714eaa6ce5310586e33b4c7e1dfbc947318330984d1d110a369c9de6da2c",
		"block_number": "11648399",
		"swap_fee": "0.0025",
		"denorms": [
			{
				"token_address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
				"token_symbol": "DAI",
				"denorm": "10.000000000"
			},
			{
				"token_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
				"token_symbol": "WETH",
				"denorm": "40.000000000"
			}
		],
		"contract_call": [
			{
				"type": "swapExactAmountIn",
				"inputs": {
					"tokenIn_symbol": "DAI",
					"tokenIn": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
					"tokenAmountIn": "236.906023596408551105",
					"tokenOut_symbol": "WETH",
					"tokenOut": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
					"minAmountOut": "0",
					"maxPrice": "115792089237316195423570985008687907853269984665640564039457.584007913129639935"
				}
			}
		],
		"action": {
			"type": "swap",
			"token_in": {
				"amount": "236.906023596408551105",
				"symbol": "DAI"
			},
			"token_out": {
				"amount": "0.21445704314241238",
				"symbol": "WETH"
			}
		}
	},
```

The structure in general is:

* timestamp: When the action happened
* tx\_hash: Transaction hash that resulted in the action. May not be unique, for example a transaction generated by an aggregator like 1inch can result in several swaps against the same pool
* block\_number: Ethereum block number where the transaction happened
* swap\_fee: Pool swap fee setting at this stage.
* denorms: Pool tokens' [denormalized weight](https://docs.balancer.finance/core-concepts/protocol/glossary) at this stage
* contract\_call: Result of parsing LOG\_CALL anonymous event, that has the exact method name and parameters of the smart contract method
* action: Outcome of the action in this pool, combining LOG\_CALL, LO&#x47;_\__&#x4A;OIN and LOG\_EXIT events getting data from BigQuery ETL project

**2. External price updates** trigger state updates to the USD price of tokens

```
{
		"timestamp": "2021-01-13T19:00:00+00:00",
		"fiat_currency": "USD",
		"action": {
			"type": "external_price_update",
			"tokens": {
				"DAI": 1.0010362320777462,
				"WETH": 1068.3949468116527
			}
		}
	},
```

The decoder has a selector to execute the relevant pool method or price update. In this version, only some pool events are used according to action type.

In every timestep we update the date time and action type.

```
    @staticmethod
    def p_action_decoder(params, step, history, current_state):
        if ActionDecoder.action_df is None:
            raise Exception('call ActionDecoder.load_actions(path_to_action.json) first')
        '''
        In this simplified model of Balancer, we have not modeled user behavior. Instead, we map events to actions.
        '''
        decoding_type = get_param(params, 'decoding_type')

        ActionDecoder.decoding_type = ActionDecodingType(decoding_type)
        idx = current_state['timestep'] + 1
        if ActionDecoder.decoding_type == ActionDecodingType.simplified:
            return ActionDecoder.p_simplified_action_decoder(idx, params, step, history, current_state)
        elif ActionDecoder.decoding_type == ActionDecodingType.contract_call:
            return ActionDecoder.p_contract_call_action_decoder(idx, params, step, history, current_state)
        elif ActionDecoder.decoding_type == ActionDecodingType.replay_output:
            return ActionDecoder.p_plot_output_action_decoder(idx, params, step, history, current_state)
        else:
            raise Exception(f'unknown decoding type {decoding_type}')

    ...
    
    @staticmethod
    def p_simplified_action_decoder(idx, params, step, history, current_state):
        action = ActionDecoder.action_df['action'][idx]
        timestamp = ActionDecoder.action_df['timestamp'][idx]
        tx_hash = ActionDecoder.action_df['tx_hash'][idx]
        if action['type'] == 'swap':
            pool_method_params = PoolMethodParamsDecoder.swap_exact_amount_in_simplified(action)
        elif action['type'] == 'join':
            pool_method_params = PoolMethodParamsDecoder.join_pool_simplified(action)
        elif action['type'] == 'join_swap':
            pool_method_params = PoolMethodParamsDecoder.join_swap_extern_amount_in_simplified(action)
        elif action['type'] == 'exit_swap':
            pool_method_params = PoolMethodParamsDecoder.exit_swap_pool_amount_in_simplified(action)
        elif action['type'] == 'exit':
            pool_method_params = PoolMethodParamsDecoder.exit_pool_simplified(action)
        elif action['type'] == 'external_price_update':
            return {'external_price_update': action['tokens'], 'change_datetime_update': timestamp, 'action_type': action['type'],
                    'pool_update': None}
        else:
            raise Exception("Action type {} unimplemented".format(action['type']))
        return {'pool_update': pool_method_params, 'change_datetime_update': timestamp, 'action_type': action['type']}
            
```
