-
-
owned this note
-
Published
Linked with GitHub
# ASE test cases
## Notable cases / questions
* 1.2.c - Case of looking up TT before it was populated.
* 1.2.d - REVERT during `lookup_target`
* When can this realistically happen? Here the legacy contract takes 32 bytes from calldata without masking and without truncating. Not sure if this could be common. Are there other more common cases?
* 1.4.b - Case of TT being populated in legacy contract
* 1.5.c-e - Not sure how common is getting address from calldata with `shr(96, calldataload(0))` (not sure how ABI usually works)
* 1.5.e - ETH burnt because TT not populated
* 1.5 - Should we also explore cases with storage like
* tx1 saves CALLER to storage + tx2 transfers to saved address
* tx1 saves address from calldata to storage + tx2 transfers to saved address
* What could be potentially interesting is TT being populated between 2 transactions (but maybe this doesn't change anything)
* 1.6.c-d - token transfer with a side-effect of populating TT
* 2.2.c - extended contract doesn't handle compressed addresses
* 2.5.e - ETH burnt in case contract doesn't handle TT lookup
* 2.6.b,f - Token contract will need to handle TT lookup
* 2.6.c,g - Even if contract handles TT lookup it doesn't help when TT is not populated
## 1. Execution in legacy contract
### 1.1 ADDRESS
| Contract code | Translation table | Expected result | Expected TT
| --- | --- | --- | ---
|`address` | `{}` | `shortaddress` | `{}`
### 1.2. Querying external account
| | Contract code | Call data | Translation table | Expected result
| --- | --- | --- | --- | ---
| a. shortaddress | `balance(shr(96, calldataload(0)))` | `shortaddress` | `{}` | `balance(shortaddress)`
| b. compressed address with a record in TT | `balance(shr(96, calldataload(0)))` | `compressed_address` | `{compressed_address: longaddress}` | `balance(longaddress)`
| c. compressed address without a record in TT | `balance(shr(96, calldataload(0)))` | `compressed_address` | `{}` | `balance(compressed_address)` - probably 0
| d. longaddress not truncated/masked| `balance(calldataload(0))` | `longaddress` | `{}` | REVERT during BALANCE
e.-... Similar for querying EXT*, CALLs and SELFDESTRUCT
### 1.3 CREATE*
Note: CREATE* opcodes don't require Translation Table.
| | Contract code | Expected result
| --- | --- | ---
| a. CREATE | `create(0, init_offset, init_size)` | `shortaddress`: `keccak256(rlp(address, nonce))[12:]`
| a. CREATE2 | `create2(0, init_offset, init_size, salt)` | `shortaddress`: `keccak256("ff" || address || salt || keccak256(init_code))[12:]`
### 1.4 Opcodes returning external address (ORIGIN, CALLER, COINBASE)
Here Callee is a legacy contract, while Caller can be legacy or extended.
| | Caller code | Callee code | TT before caller code | Expected result | Expected TT
|--- | --- | --- | --- | --- | ---
| a. Legacy caller | `call(gas, shortaddress_callee, 0, 0, 0, 0, 0)` | `caller()` | `{}` | `shortaddress_caller` | `{}`
| b. Extended caller | `call(gas, zero_padded_shortaddress_callee, 0, 0, 0, 0, 0)` | `caller()` | `{}` | `compressed_address_caller` | `{compressed_address_caller: longaddress_caller}`
### 1.5 ETH transfer / CALL in a contract
The destination address can come either from an opcode (`CALLER`, `ORIGIN`, `COINBASE`) or from calldata (e.g. transfer from a multisig) or from storage value (e.g. auction contract refunding lost bidders).
TODO The address saved in storage was taken in the first place from either an opcode or calldata. Can we assume no additional issues related to saving address into storage?
| | Contract code | Call data | Translation table | Expected result | Expected TT
| --- | --- | --- | --- | --- | ---
| a. To legacy caller | `call(21000, caller(), value, 0, 0, 0, 0)` | | `{}` | value transferred to caller | `{}`
| b. To extended caller | `call(21000, caller(), value, 0, 0, 0, 0)` | | `{}` | value transferred to caller | `{compressed_address_caller: longaddress_caller}`
| c. To legacy address in calldata | `call(21000, shr(96, calldataload(0)), value, 0, 0, 0, 0)` | `shortaddress` | `{}` | value transferred to `shortaddress` | `{}`
| d. To compressed address in calldata with a record in TT | `call(21000, shr(96, calldataload(0)), value, 0, 0, 0, 0)` | `compressed_address` | `{compressed_address: longaddress}` | value transferred to `longaddress` | `{compressed_address: longaddress}`
| e. To compressed address in calldata without a record in TT | `call(21000, shr(96, calldataload(0)), value, 0, 0, 0, 0)` | `compressed_address` | `{}` | value transferred to `compressed_address` -> burnt | `{}`
TODO the case of `longaddress` passed in calldata to ABIv2 (resulting in revert) and non-ABIv2.
### 1.6 Token transfer
Here sender address is taken from `CALLER` and destination address is taken from calldata.
| | Caller | _to | TT | Expected contract storage | Expected TT
|--- | --- | --- | --- | --- | --- | ---
| a. | legacy | `shortaddress` | `{}` | `shortaddress` -> `value` | `{}` |
| b. | legacy | `compressed_address` | `{}` | `compressed_address` -> `value` | `{}` |
| c. | extended | `shortaddress` | `{}` | `shortaddress` -> value | `{compressed_address_caller: longaddress_caller}` |
| d. | extended | `compressed_address` | `{}` | `compressed_address` -> `value` | `{compressed_address_caller: longaddress_caller}`
Note: Transferring *to* `compressed_address` doesn't require any records in TT. Transferring *from* extended address (seen as `compressed_address` inside legacy contract) doesn't require pre-existing records in TT, but will add a record when `CALLER` opcode is executed.
TODO the case of `longaddress` _to passed to non-ABIv2
## 2. Execution in extended contract
### 2.1 ADDRESS
| Contract code | Translation table | Expected result | Expected TT
| --- | --- | --- | ---
|`address` | `{}` | `longaddress` | `{}`
### 2.2 Querying external account
| | Contract code | Call data | TT | Expected result | Expected TT
| --- | --- | --- | --- | --- | ---
| a. longaddress | `balance(calldataload(0)` | `longaddress` | `{}` | `balance(longaddress)` | `{compressed_address: longaddress}`
| b. 0-padded shortaddress | `balance(calldataload(0))` | `shortaddress` 0-padded to 32 bytes | `{}` | `balance(shortaddress)` | `{}`
| с. 0-padded compressed_address | `balance(calldataload(0))` | `compressed_address` 0-padded to 32 bytes | `{}` | `balance(compressed_address)` - probably 0 | `{}`
в.-... Similar for querying EXT*, CALLs and SELFDESTRUCT
### 2.3 CREATE*
Note: CREATE* opcodes don't modify Translation Table.
| | Contract code | Expected result
| --- | --- | ---
| a. CREATE | `create(0, init_offset, init_size)` | `longaddress`: `010000000000 || keccak256(rlp(address, nonce))[6:]`
| a. CREATE2 | `create2(0, init_offset, init_size, salt)` | `longaddress`: `010000000000 || keccak256("ff" || address || salt || keccak256(init_code))[6:]`
### 2.4 Opcodes returning external address (ORIGIN, CALLER, COINBASE)
Here Callee is an extended contract, while a Caller can be legacy (requires TT record for a call to work) or extended.
| | Caller code | Callee code | TT before caller code | Expected result | Expected TT
|--- | --- | --- | --- | --- | ---
| a. Legacy caller | `call(gas, compressed_address_callee, 0, 0, 0, 0, 0)` | `caller()` | `{compressed_address_callee: longaddress_callee}` | `shortaddress_caller` 0-padded to 32 bytes | ``{compressed_address_callee: longaddress_callee}``
| b. Extended caller | `call(gas, longaddress_calee, 0, 0, 0, 0, 0)` | `caller()` | `{}` | `longaddress_caller` | `{compressed_address_callee: longaddress_callee}`
### 2.5 ETH transfer
| | Contract code | Call data | Translation table | Expected result | Expected TT
| --- | --- | --- | --- | --- | ---
| a. To legacy caller | `call(21000, caller(), value, 0, 0, 0, 0)` | | `{}` | value transferred to caller | `{}`
| b. To extended caller | `call(21000, caller(), value, 0, 0, 0, 0)` | | `{}` | value transferred to caller | `{compressed_address_caller: longaddress_caller}`
| c. To shortaddress in calldata | `call(21000, calldataload(0), value, 0, 0, 0, 0)` | `shortaddress` 0-padded to 32 bytes | `{}` | value transferred to `shortaddress` | `{}`
| d. To longaddress in calldata | `call(21000, calldataload(0), value, 0, 0, 0, 0)` | `longaddress` | `{}` | value transferred to `longaddress` | `{compressed_address: longaddress}`
| e. To compressed address in calldata | `call(21000, calldataload(0), value, 0, 0, 0, 0)` | `compressed_address` 0-padded to 32 bytes | `{compressed_address: longaddress}` | value transferred to `compressed_address` -> burnt | `{compressed_address: longaddress}`
### 2.6 Token transfer
Assuming extended token contract accepts 32-byte value in _to.
| | Caller | _to | TT | Expected contract storage
|--- | --- | --- | --- | ---
| a. | extended | 0-padded `shortaddress` | `{}` | `shortaddress` -> `value` |
| b. Contract will need to handle TT lookup itself | extended | 0-padded `compressed_address` | `{compressed_address: longaddress}` | `longaddress` -> `value` |
| c. Token burnt | extended | 0-padded `compressed_address` | `{}` | `compressed_address` -> `value` |
| d. | extended | `longaddress` | `{}` | `longaddress` -> `value` |
| e. | legacy | 0-padded `shortaddress` | `{}` | `shortaddress` -> `value` |
| f. Contract will need to handle TT lookup itself | legacy | 0-padded `compressed_address` | `{compressed_address: longaddress}` | `longaddress` -> `value` |
| g. Token burnt | legacy | 0-padded `compressed_address` | `{}` | `compressed_address` -> `value` |
| h. | legacy | `longaddress` | `{}` | `longaddress` -> `value` |