-
Notifications
You must be signed in to change notification settings - Fork 0
Add Gasless System #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: lightlink
Are you sure you want to change the base?
Changes from 56 commits
f14d554
60393ae
62d5c34
e02a2b2
5bd11e9
421f068
eaccbf6
33c1689
cdfcc58
f60d354
7590f0d
9595d34
233909f
e48fb61
8cc4298
aaf52a1
7187a5f
0d41faa
0aeb0ba
b9651da
bb18aba
cce28e8
ea50c87
33c7031
f7b2e74
d544e64
08bad5c
3bfd0f3
dd83eac
d298bf1
dd7c785
92fdad2
c31d25f
e49ea72
710577b
e19a224
75fb049
d8395f6
6ec926e
9a0dcd7
5e1029f
bf4b5a2
911be36
41c9f05
a4fbb67
c759aa8
b32dca2
61de7f6
a6cd075
51320ba
dd7f0f8
1af1616
02a2c16
01f9348
85498de
9d017b3
0385360
80f6d79
31b7db9
d8f9205
2735c7e
5fc91fa
b44e5ec
3df3691
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,89 @@ | ||||||||||||||||||||||||||||
| name: Test, Build Image & Push to ECR | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||
| branches: | ||||||||||||||||||||||||||||
| - main # Trigger the workflow on pushes to the main branch | ||||||||||||||||||||||||||||
| tags: | ||||||||||||||||||||||||||||
| - "**" # Trigger the workflow on tags including hierarchical tags like v1.0/beta | ||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||
| types: [opened, synchronize] # Trigger the workflow when a PR is opened or updated | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||
| RELEASE_REVISION: ${{ github.sha }} | ||||||||||||||||||||||||||||
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||||||||||||||||||||||||||||
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||||||||||||||||||||||||||||
| AWS_REGION: ${{ secrets.AWS_REGION }} | ||||||||||||||||||||||||||||
| ECR_REPOSITORY: ll-geth | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||
| # test: | ||||||||||||||||||||||||||||
| # name: Run Go Tests | ||||||||||||||||||||||||||||
| # runs-on: ubuntu-latest | ||||||||||||||||||||||||||||
| # steps: | ||||||||||||||||||||||||||||
| # - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # - name: Set up Go | ||||||||||||||||||||||||||||
| # uses: actions/setup-go@v5 | ||||||||||||||||||||||||||||
| # with: | ||||||||||||||||||||||||||||
| # go-version: 1.21.5 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # - name: Test | ||||||||||||||||||||||||||||
| # run: go test -v ./... | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| release: | ||||||||||||||||||||||||||||
| # needs: test | ||||||||||||||||||||||||||||
| name: Build Image & Push to ECR | ||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Gate ECR push on push-events to main/tags only. Avoid attempting to push on pull_request events and from forks. Apply: release:
+ if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags/'))
# needs: test
name: Build Image & Push to ECR
runs-on: ubuntu-latest
+ permissions:
+ contents: read📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||
| ref: ${{ github.sha }} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Configure AWS credentials | ||||||||||||||||||||||||||||
| uses: aws-actions/configure-aws-credentials@v4 | ||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||
| aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} | ||||||||||||||||||||||||||||
| aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} | ||||||||||||||||||||||||||||
| aws-region: ${{ env.AWS_REGION }} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Login to Amazon ECR | ||||||||||||||||||||||||||||
| id: login-ecr | ||||||||||||||||||||||||||||
| uses: aws-actions/amazon-ecr-login@v2 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Set up Docker Buildx | ||||||||||||||||||||||||||||
| id: buildx | ||||||||||||||||||||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Docker cache layers | ||||||||||||||||||||||||||||
| uses: actions/cache@v4 | ||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||
| path: /tmp/.buildx-cache | ||||||||||||||||||||||||||||
| key: ${{ runner.os }}-single-buildx-${{ github.sha }} | ||||||||||||||||||||||||||||
| restore-keys: | | ||||||||||||||||||||||||||||
| ${{ runner.os }}-single-buildx | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Get the version tag or short SHA | ||||||||||||||||||||||||||||
| id: get-tag | ||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||
| if [[ "${{ github.ref }}" == refs/tags/* ]]; then | ||||||||||||||||||||||||||||
| echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||
| echo "version=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Push Image | ||||||||||||||||||||||||||||
| uses: docker/build-push-action@v5 | ||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||
| context: . | ||||||||||||||||||||||||||||
| push: true | ||||||||||||||||||||||||||||
| tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.get-tag.outputs.version }} | ||||||||||||||||||||||||||||
| cache-from: type=local,src=/tmp/.buildx-cache | ||||||||||||||||||||||||||||
| cache-to: type=local,dest=/tmp/.buildx-cache-new | ||||||||||||||||||||||||||||
| build-args: VERSION=${{ steps.get-tag.outputs.version }} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| - name: Move cache | ||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||
| rm -rf /tmp/.buildx-cache | ||||||||||||||||||||||||||||
| mv /tmp/.buildx-cache-new /tmp/.buildx-cache | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,3 +43,6 @@ profile.cov | |
| .vscode | ||
|
|
||
| tests/spec-tests/ | ||
| devdata/ | ||
| password.txt | ||
| plain_key.txt | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| package core | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math/big" | ||
|
|
||
| "github.com/ethereum/go-ethereum/accounts/abi" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/ethereum/go-ethereum/params" | ||
| ) | ||
|
|
||
| // Event signature | ||
| var CreditsUsedEventSignature = crypto.Keccak256Hash([]byte("CreditsUsed(address,address,uint256)")) | ||
|
|
||
| var ( | ||
| addressType, _ = abi.NewType("address", "", nil) | ||
| uint256Type, _ = abi.NewType("uint256", "", nil) | ||
|
|
||
| // Pre-built arguments for CreditsUsed event | ||
| CreditsUsedEventArgs = abi.Arguments{ | ||
| {Type: addressType}, // caller (not indexed) | ||
| {Type: uint256Type}, // gasUsed (not indexed) | ||
| } | ||
sledro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) | ||
|
|
||
| // StateReader defines the minimal interface needed for gasless transaction validation | ||
| // This allows ValidateGaslessTx to work with any state implementation that can read storage | ||
| type StateReader interface { | ||
| GetState(addr common.Address, hash common.Hash) common.Hash | ||
| } | ||
|
|
||
| // GasStation struct storage slots structs | ||
| type GasStationStorageSlots struct { | ||
| StructBaseSlotHash common.Hash | ||
| CreditSlotHash common.Hash | ||
| WhitelistEnabledSlotHash common.Hash | ||
| NestedWhitelistMapBaseSlotHash common.Hash | ||
| SingleUseEnabledSlotHash common.Hash | ||
| UsedAddressesMapBaseSlotHash common.Hash | ||
| } | ||
|
|
||
| // calculateGasStationSlots computes the storage slot hashes for a specific | ||
| // registered contract within the GasStation's `contracts` mapping. | ||
| // It returns the base slot for the struct (holding packed fields), the slot for credits, | ||
| // the slot for whitelistEnabled, and the base slot for the nested whitelist mapping. | ||
| func CalculateGasStationSlots(registeredContractAddress common.Address) GasStationStorageSlots { | ||
| gasStationStorageSlots := GasStationStorageSlots{} | ||
|
|
||
| // ERC-7201 storage location for GasStationStorage | ||
| // bytes32 private constant GasStationStorageLocation = keccak256(abi.encode(uint256(keccak256("gasstation.storage")) - 1)) & ~bytes32(uint256(0xff)); | ||
| // Computed value: 0xc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00 | ||
| gasStationStorageLocation := common.HexToHash("0xc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00") | ||
|
|
||
| // The 'contracts' mapping is at offset 1 from the storage location | ||
| // (dao is at offset 0, contracts is at offset 1) | ||
| contractsMapSlot := new(big.Int).Add(gasStationStorageLocation.Big(), big.NewInt(1)) | ||
|
|
||
| // Calculate the base slot for the struct entry in the mapping | ||
| keyPadded := common.LeftPadBytes(registeredContractAddress.Bytes(), 32) | ||
| mapSlotPadded := common.LeftPadBytes(contractsMapSlot.Bytes(), 32) | ||
| combined := append(keyPadded, mapSlotPadded...) | ||
| gasStationStorageSlots.StructBaseSlotHash = crypto.Keccak256Hash(combined) | ||
|
|
||
| // Calculate subsequent slots by adding offsets to the base slot hash | ||
| // New struct layout: bool registered, bool active, address admin (all packed in slot 0) | ||
| // uint256 credits (slot 1), bool whitelistEnabled (slot 2), mapping whitelist (slot 3) | ||
| // bool singleUseEnabled (slot 4), mapping usedAddresses (slot 5) | ||
| structBaseSlotBig := gasStationStorageSlots.StructBaseSlotHash.Big() | ||
|
|
||
| // Slot for 'credits' (offset 1 from base - after the packed bools and address) | ||
| creditsSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(1)) | ||
| gasStationStorageSlots.CreditSlotHash = common.BigToHash(creditsSlotBig) | ||
|
|
||
| // Slot for 'whitelistEnabled' (offset 2 from base) | ||
| whitelistEnabledSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(2)) | ||
| gasStationStorageSlots.WhitelistEnabledSlotHash = common.BigToHash(whitelistEnabledSlotBig) | ||
|
|
||
| // Base slot for the nested 'whitelist' mapping (offset 3 from base) | ||
| nestedWhitelistMapBaseSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(3)) | ||
| gasStationStorageSlots.NestedWhitelistMapBaseSlotHash = common.BigToHash(nestedWhitelistMapBaseSlotBig) | ||
|
|
||
| // Slot for 'singleUseEnabled' (offset 4 from base) | ||
| singleUseEnabledSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(4)) | ||
| gasStationStorageSlots.SingleUseEnabledSlotHash = common.BigToHash(singleUseEnabledSlotBig) | ||
|
|
||
| // Base slot for the nested 'usedAddresses' mapping (offset 5 from base) | ||
| usedAddressesMapBaseSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(5)) | ||
| gasStationStorageSlots.UsedAddressesMapBaseSlotHash = common.BigToHash(usedAddressesMapBaseSlotBig) | ||
|
|
||
| return gasStationStorageSlots | ||
| } | ||
|
|
||
| func ValidateGaslessTx(to *common.Address, from common.Address, gasLimit uint64, sdb StateReader) (*big.Int, *big.Int, *GasStationStorageSlots, error) { | ||
| if to == nil { | ||
| return nil, nil, nil, fmt.Errorf("gasless txn must have a valid to address") | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if gasLimit == 0 { | ||
| return nil, nil, nil, fmt.Errorf("gasless txn must have a non-zero gas limit") | ||
| } | ||
|
|
||
| // Calculate GasStation storage slots | ||
| gasStationStorageSlots := CalculateGasStationSlots(*to) | ||
|
|
||
| // Get the storage for the GaslessContract struct for the given address | ||
| storageBaseSlot := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.StructBaseSlotHash) | ||
|
|
||
| // Extract the registered and active bytes from the storage slot | ||
| isRegistered := storageBaseSlot[31] == 0x01 | ||
| isActive := storageBaseSlot[30] == 0x01 | ||
|
|
||
| if !isRegistered { | ||
| return nil, nil, nil, fmt.Errorf("gasless transaction to unregistered address") | ||
| } | ||
|
|
||
| if !isActive { | ||
| return nil, nil, nil, fmt.Errorf("gasless transaction to inactive address") | ||
| } | ||
|
|
||
| // Get the available credits from the credits storage slot in the GasStation contract for the given address | ||
| availableCredits := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.CreditSlotHash) | ||
|
|
||
| // Convert credits (Hash) and tx gas (uint64) to big.Int for comparison | ||
| availableCreditsBig := new(big.Int).SetBytes(availableCredits.Bytes()) | ||
| txRequiredCreditsBig := new(big.Int).SetUint64(gasLimit) | ||
|
|
||
| // Check if contract has enough available credits to cover the cost of the tx | ||
| if availableCreditsBig.Cmp(txRequiredCreditsBig) < 0 { | ||
| return nil, nil, nil, fmt.Errorf("gasless transaction has insufficient credits: have %v, need %v", availableCreditsBig, txRequiredCreditsBig) | ||
| } | ||
|
|
||
| // Get the whitelist enabled slot | ||
| whitelistEnabled := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.WhitelistEnabledSlotHash) | ||
|
|
||
| // Get the whitelist enabled byte from the whitelist enabled slot | ||
| isWhitelistEnabled := whitelistEnabled[31] == 0x01 | ||
|
|
||
| if isWhitelistEnabled { | ||
| // Calculate slot for the specific user in the nested whitelist map | ||
| userWhitelistSlotHash := calculateNestedMappingSlot(from, gasStationStorageSlots.NestedWhitelistMapBaseSlotHash) | ||
|
|
||
| // Get the whitelist status for the specific user | ||
| userWhitelist := sdb.GetState(params.GasStationAddress, userWhitelistSlotHash) | ||
|
|
||
| // Check if the user is whitelisted | ||
| userWhitelistByte := userWhitelist[31] | ||
| isUserWhitelistStorage := userWhitelistByte == 0x01 | ||
|
|
||
| if !isUserWhitelistStorage { | ||
| return nil, nil, nil, fmt.Errorf("gasless transaction to non-whitelisted address") | ||
| } | ||
| } | ||
|
|
||
| // Check single-use mode if enabled | ||
| singleUseEnabled := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.SingleUseEnabledSlotHash) | ||
| isSingleUseEnabled := singleUseEnabled[31] == 0x01 | ||
|
|
||
| if isSingleUseEnabled { | ||
| // Calculate slot for the specific user in the nested usedAddresses map | ||
| userUsedSlotHash := calculateNestedMappingSlot(from, gasStationStorageSlots.UsedAddressesMapBaseSlotHash) | ||
|
|
||
| // Get the used status for the specific user | ||
| userUsed := sdb.GetState(params.GasStationAddress, userUsedSlotHash) | ||
|
|
||
| // Check if the user has already used gasless transactions | ||
| isUserAlreadyUsed := userUsed[31] == 0x01 | ||
|
|
||
| if isUserAlreadyUsed { | ||
| return nil, nil, nil, fmt.Errorf("gasless transaction from address that has already used single-use gasless functionality") | ||
| } | ||
| } | ||
|
|
||
| return availableCreditsBig, txRequiredCreditsBig, &gasStationStorageSlots, nil | ||
| } | ||
|
|
||
| // calculateNestedMappingSlot computes the storage slot hash for a nested mapping | ||
| func calculateNestedMappingSlot(key common.Address, baseSlot common.Hash) common.Hash { | ||
| keyPadded := common.LeftPadBytes(key.Bytes(), 32) | ||
| mapBaseSlotPadded := common.LeftPadBytes(baseSlot.Bytes(), 32) | ||
| combined := append(keyPadded, mapBaseSlotPadded...) | ||
| return crypto.Keccak256Hash(combined) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop exporting AWS secrets as top-level env; pass secrets directly to the action.
Top-level env propagates to all steps (including shell), increasing leak risk. Feed secrets directly into aws-actions instead.
Apply these diffs:
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: - aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }}Also applies to: 44-50
🤖 Prompt for AI Agents